├── .circleci
└── config.yml
├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── build.gradle
├── config
├── checkstyle
│ ├── checkstyle.xml
│ └── suppressions.xml
├── header.txt
└── intellij_code_style.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
├── src
├── main
│ └── java
│ │ └── com
│ │ └── aitusoftware
│ │ └── aether
│ │ ├── Aether.java
│ │ ├── CountersPoller.java
│ │ ├── aggregation
│ │ ├── RateBucket.java
│ │ ├── RateConsumer.java
│ │ └── StreamRate.java
│ │ ├── annotation
│ │ └── CallerOwned.java
│ │ ├── event
│ │ ├── CharSequenceUtil.java
│ │ ├── ConsolePrinter.java
│ │ ├── CounterEventHandler.java
│ │ ├── CounterRepository.java
│ │ ├── CounterSnapshotListener.java
│ │ ├── CounterValueListener.java
│ │ ├── RateMonitor.java
│ │ ├── StreamKey.java
│ │ └── SystemSnapshot.java
│ │ ├── model
│ │ ├── ChannelSessionKey.java
│ │ ├── ChannelStatus.java
│ │ ├── ChannelType.java
│ │ ├── PublisherCounterSet.java
│ │ ├── SessionKeyed.java
│ │ ├── SubscriberCounterSet.java
│ │ └── SystemCounters.java
│ │ └── transport
│ │ ├── ChannelConfig.java
│ │ ├── CounterSnapshotPublisher.java
│ │ ├── CounterSnapshotSubscriber.java
│ │ ├── SnapshotDeserialiser.java
│ │ ├── SnapshotSerialiser.java
│ │ └── Versions.java
└── test
│ └── java
│ └── com
│ └── aitusoftware
│ └── aether
│ ├── CountersPollerTest.java
│ ├── aggregation
│ └── StreamRateTest.java
│ ├── event
│ ├── CounterEventHandlerTest.java
│ ├── CounterRepositoryTest.java
│ └── RateMonitorTest.java
│ └── transport
│ └── SnapshotSerialiserTest.java
└── version.txt
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/openjdk:8-jdk
6 | working_directory: ~/repo
7 | environment:
8 | JVM_OPTS: -Xmx3200m
9 | TERM: dumb
10 |
11 | steps:
12 | - checkout
13 | - restore_cache:
14 | keys:
15 | - v1-dependencies-{{ checksum "build.gradle" }}
16 | # fallback to using the latest cache if no exact match is found
17 | - v1-dependencies-
18 |
19 | - run: gradle dependencies
20 |
21 | - save_cache:
22 | paths:
23 | - ~/.gradle
24 | key: v1-dependencies-{{ checksum "build.gradle" }}
25 | - run: gradle check
26 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gradle"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Gradle project-specific cache directory
2 | .gradle
3 |
4 | # Ignore Gradle build output directory
5 | build
6 | *.iml
7 | .idea/
8 | out/
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/aitusoftware/aether)
2 |
3 | # Aether
4 |
5 | Monitoring for the [Aeron](https://github.com/real-logic/aeron) messaging system.
6 |
7 | ## Usage
8 |
9 | An instance of the Aether client reads counters from one or more Aeron `MediaDriver`s, and
10 | publishes the counter data to a collector. The collector's function is to build a system-wide
11 | view of the Aeron message flows, matching publishers to subscribers.
12 |
13 | ### Running an Aether Collector
14 |
15 | ```java
16 | // launch a MediaDriver for publishing counters
17 | final MediaDriver driver = MediaDriver.launchEmbedded(new MediaDriver.Context()
18 | .threadingMode(ThreadingMode.SHARED)
19 | .sharedIdleStrategy(new SleepingMillisIdleStrategy(1L)));
20 |
21 | // create a publish that will send out counter snapshots
22 | final CounterSnapshotPublisher snapshotPublisher = new CounterSnapshotPublisher(
23 | new CounterSnapshotPublisher.Context()
24 | .aeronDirectoryName(driver.aeronDirectoryName()));
25 |
26 | // create an Aether agent that will periodically read and publish the counters
27 | final Aether aether = Aether.launch(new Aether.Context()
28 | .monitoringLocations(Collections.singletonList(
29 | new Aether.MonitoringLocation("default", "/path/to/monitored-media-driver")))
30 | .counterSnapshotListener(snapshotPublisher)
31 | .aeronDirectoryName(driver.aeronDirectoryName())
32 | .threadingMode(Aether.ThreadingMode.THREADED));
33 | ```
34 |
35 | ### Running an Aether Aggregator
36 |
37 | ```java
38 | // create a MediaDriver to receive counter snapshots
39 | final MediaDriver mediaDriver = MediaDriver.launchEmbedded(new MediaDriver.Context()
40 | .threadingMode(ThreadingMode.SHARED)
41 | .sharedIdleStrategy(new SleepingMillisIdleStrategy(1L)));
42 |
43 | // create a subscription to deserialise received snapshots
44 | final CounterSnapshotSubscriber counterSnapshotSubscriber =
45 | new CounterSnapshotSubscriber(new CounterSnapshotSubscriber.Context()
46 | .aeronDirectoryName(mediaDriver.aeronDirectoryName())
47 | .counterSnapshotListener(new ConsolePrinter()));
48 |
49 | // poll subscription to update system snapshot
50 | counterSnapshotSubscriber.doWork();
51 | ```
52 |
53 | ## Output
54 |
55 | The default `ConsolePrinter` will display aggregate information for all configured contexts:
56 |
57 | ```
58 | -----------------------------------------
59 | ===== System counters for "server" =====
60 | Bytes sent: 0
61 | Bytes received: 27584
62 | NAKs sent: 0
63 | NAKs received: 0
64 | Errors: 0
65 | Client timeouts: 0
66 | ===== System counters for "client" =====
67 | Bytes sent: 27584
68 | Bytes received: 0
69 | NAKs sent: 0
70 | NAKs received: 0
71 | Errors: 0
72 | Client timeouts: 0
73 | ===== Monitoring 3 channels =====
74 | ==== aeron:udp?endpoint=localhost:54567/37 ====
75 |
76 | ---- Publisher Session 2033564099 ----
77 | | publisher position: 19584
78 | | publisher limit: 26240
79 | | sender position: 26240
80 | | sender limit: 133440
81 | ---- Subscriber ----
82 | | receiver position: 26240
83 | | receiver HWM: 26240
84 | | position (1): 3520
85 | | position (2): 2432
86 | | position (3): 2368
87 | -----------------------------------------
88 | ==== aeron:udp?endpoint=localhost:54587/37 ====
89 |
90 | ---- Publisher Session 2033564101 ----
91 | | publisher position: 320
92 | | publisher limit: 576
93 | | sender position: 576
94 | | sender limit: 131264
95 | ---- Subscriber ----
96 | | receiver position: 576
97 | | receiver HWM: 576
98 | | position (6): 192
99 | -----------------------------------------
100 | ```
101 |
102 | ## As an Application
103 |
104 | ### Collector
105 |
106 | To run Aether as a command-line application, runtime configuration can be used:
107 |
108 | #### aether-collector.properties
109 |
110 | ```
111 | # Describes the locations of MediaDrivers that should be monitored
112 | aether.monitoringLocations=client:/path/to/client/media-driver;server:/path/to/server/media-driver
113 | # Tells Aether to publish snapshot over an Aeron Publication
114 | aether.transport=AERON
115 | # Tells Aether to monitor locations and publish
116 | aether.mode=PUBLISHER
117 | # Describes the location where Aether should launch its own MediaDriver for publishing
118 | aeron.dir=/path/to/aether-publisher-media-driver
119 | # Describe the endpoint to publish data to
120 | aether.transport.channel=aeron:udp?endpoint=monitoring-host:18996
121 | ```
122 |
123 | Then run the collector:
124 |
125 | ```
126 | java -cp /path/to/aeron-driver.jar:/path/to/aether.jar \
127 | com.aitusoftware.aether.Aether /path/to/aether-collector.properties
128 | ```
129 |
130 | ### Aggregator
131 |
132 | #### aether-aggregator.properties
133 |
134 | ```
135 | # Tells Aether to receive snapshot over an Aeron Publication
136 | aether.transport=AERON
137 | # Tells Aether to subscribe to events
138 | aether.mode=SUBSCRIBER
139 | # Describes the location where Aether should launch its own MediaDriver for subscribing
140 | aeron.dir=/path/to/aether-subscriber-media-driver
141 | # Describe the endpoint to receive data on
142 | aether.transport.channel=aeron:udp?endpoint=monitoring-host:18996
143 | ```
144 |
145 | Then run the aggregator:
146 |
147 | ```
148 | java -cp /path/to/aeron-driver.jar:/path/to/aether.jar \
149 | com.aitusoftware.aether.Aether /path/to/aether-aggregator.properties
150 | ```
151 |
152 |
153 | ## Visualising snapshot data
154 |
155 | [Aether-Net](https://github.com/aitusoftware/aether-net) provides a simple UI to display the counter snapshots.
156 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'net.minecrell.licenser' version '0.4.1' apply false
3 | id 'java'
4 | }
5 | apply plugin: 'java'
6 | def aetherVersion = file('version.txt').text.trim()
7 | group = 'com.aitusoftware'
8 | version = aetherVersion
9 |
10 | defaultTasks 'clean', 'build'
11 |
12 | ext {
13 | fullName = 'Aether'
14 | fullDescription = 'Aether: Monitoring for Aeron'
15 | teamName = 'Aitu Software Limited'
16 | siteUrl = 'https://github.com/aitusoftware/aether'
17 | sourceUrl = 'https://github.com/aitusoftware/aether.git'
18 |
19 | javaCompilerExecutable = System.env['JAVA_HOME'] ? System.env['JAVA_HOME'] + '/bin/javac' : 'javac'
20 | if (!project.hasProperty('sonatypeUrl')) sonatypeUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
21 | if (!project.hasProperty('sonatypeUsername')) sonatypeUsername = ''
22 | if (!project.hasProperty('sonatypePassword')) sonatypePassword = ''
23 | }
24 |
25 | def projectPom = {
26 | name = fullName
27 | description = fullDescription
28 | url = siteUrl
29 | packaging = 'pom'
30 |
31 | scm {
32 | url = "$sourceUrl"
33 | connection = "scm:git:https://github.com/aitusoftware.com/recall.git"
34 | developerConnection = "scm:git:https://github.com/aitusoftware.com/recall.git"
35 | }
36 |
37 | licenses {
38 | license {
39 | name = 'The Apache Software License, Version 2.0'
40 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
41 | distribution = 'repo'
42 | }
43 | }
44 |
45 | developers {
46 | developer {
47 | id = 'epickrram'
48 | name = 'Mark Price'
49 | email = 'recall@aitusoftware.com'
50 | }
51 | }
52 | }
53 |
54 | repositories {
55 | jcenter()
56 | }
57 |
58 | apply plugin: 'checkstyle'
59 | apply plugin: 'signing'
60 | apply plugin: 'maven'
61 | apply plugin: 'net.minecrell.licenser'
62 |
63 | test {
64 | useJUnitPlatform()
65 | }
66 |
67 | checkstyle {
68 | toolVersion = '8.33'
69 | }
70 |
71 | license {
72 | header = new File(rootDir, 'config/header.txt')
73 | newLine = false
74 | }
75 | task sourcesJar(type: Jar) {
76 | classifier = 'sources'
77 | from sourceSets.main.allSource
78 | }
79 |
80 | task javadocJar(type: Jar, dependsOn: javadoc) {
81 | classifier = 'javadoc'
82 | from javadoc.destinationDir
83 | }
84 | artifacts {
85 | archives sourcesJar, javadocJar
86 | }
87 |
88 | javadoc {
89 | }
90 |
91 | signing {
92 | required { gradle.taskGraph.hasTask('uploadArchives') }
93 | sign configurations.archives
94 | }
95 |
96 | uploadArchives {
97 | repositories.mavenDeployer {
98 | beforeDeployment { deployment -> signing.signPom(deployment) }
99 |
100 | repository(url: sonatypeUrl) {
101 | authentication(userName: sonatypeUsername, password: sonatypePassword)
102 | }
103 |
104 | pom.project(projectPom)
105 | }
106 | }
107 |
108 | dependencies {
109 | checkstyle 'com.puppycrawl.tools:checkstyle:8.36.2'
110 | compile 'io.aeron:aeron-driver:1.30.0'
111 | compile 'org.agrona:agrona:1.7.2'
112 |
113 | testCompile 'com.google.truth:truth:0.42'
114 | testCompile 'org.junit.jupiter:junit-jupiter-api:5.6.2'
115 | testCompile 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
116 | }
117 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
157 |
159 |
160 |
161 |
162 |
163 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/config/checkstyle/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/config/header.txt:
--------------------------------------------------------------------------------
1 | Copyright 2019-2020 Aitu Software Limited.
2 |
3 | https://aitusoftware.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
--------------------------------------------------------------------------------
/config/intellij_code_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aitusoftware/aether/65c144cfeade4d1f3eaa0eaa8388d1e78297d7db/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS='"-Xmx64m"'
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS="-Xmx64m"
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'aether'
2 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/Aether.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.Optional;
23 |
24 | import com.aitusoftware.aether.event.ConsolePrinter;
25 | import com.aitusoftware.aether.event.CounterEventHandler;
26 | import com.aitusoftware.aether.event.CounterRepository;
27 | import com.aitusoftware.aether.event.CounterSnapshotListener;
28 | import com.aitusoftware.aether.model.PublisherCounterSet;
29 | import com.aitusoftware.aether.model.SubscriberCounterSet;
30 | import com.aitusoftware.aether.transport.CounterSnapshotPublisher;
31 | import com.aitusoftware.aether.transport.CounterSnapshotSubscriber;
32 |
33 | import org.agrona.CloseHelper;
34 | import org.agrona.SystemUtil;
35 | import org.agrona.concurrent.Agent;
36 | import org.agrona.concurrent.AgentRunner;
37 | import org.agrona.concurrent.EpochClock;
38 | import org.agrona.concurrent.ShutdownSignalBarrier;
39 | import org.agrona.concurrent.SleepingMillisIdleStrategy;
40 | import org.agrona.concurrent.SystemEpochClock;
41 |
42 | import io.aeron.Aeron;
43 | import io.aeron.CommonContext;
44 | import io.aeron.driver.MediaDriver;
45 |
46 | /**
47 | * Agent used to read Aeron counters and publish snapshots to the configured {@code CounterSnapshotListener}.
48 | */
49 | public final class Aether implements Agent, AutoCloseable
50 | {
51 | private final CountersPoller[] countersPoller;
52 | private final AgentRunner agentRunner;
53 | private final Aeron aeronClient;
54 | private final MediaDriver mediaDriver;
55 | private final CounterSnapshotSubscriber counterSnapshotSubscriber;
56 | private CounterSnapshotPublisher counterSnapshotPublisher;
57 |
58 | /**
59 | * Construct a new instance with the given context.
60 | *
61 | * @param context configuration context
62 | */
63 | private Aether(final Context context)
64 | {
65 | context.validate();
66 | if (context.launchEmbeddedMediaDriver())
67 | {
68 | mediaDriver = MediaDriver.launchEmbedded();
69 | aeronClient = Aeron.connect(new Aeron.Context().useConductorAgentInvoker(true)
70 | .aeronDirectoryName(mediaDriver.aeronDirectoryName()));
71 | }
72 | else
73 | {
74 | mediaDriver = null;
75 | aeronClient = Aeron.connect(new Aeron.Context().useConductorAgentInvoker(true)
76 | .aeronDirectoryName(context.aeronDirectoryName()));
77 | }
78 | if (context.threadingMode() == ThreadingMode.THREADED)
79 | {
80 | agentRunner = new AgentRunner(new SleepingMillisIdleStrategy(1L), e ->
81 | {
82 | },
83 | aeronClient.addCounter(10000, "aether-errors"), this);
84 | AgentRunner.startOnThread(agentRunner);
85 | }
86 | else
87 | {
88 | agentRunner = null;
89 | }
90 | final EpochClock epochClock = new SystemEpochClock();
91 | if (context.transport() == Transport.AERON)
92 | {
93 | if (context.mode() == Mode.SUBSCRIBER)
94 | {
95 | counterSnapshotSubscriber = new CounterSnapshotSubscriber(
96 | new CounterSnapshotSubscriber.Context()
97 | .counterSnapshotListener(context.counterSnapshotListener())
98 | .aeronClient(aeronClient));
99 | }
100 | else
101 | {
102 | counterSnapshotPublisher = new CounterSnapshotPublisher(
103 | new CounterSnapshotPublisher.Context().aeronClient(aeronClient));
104 | context.counterSnapshotListener(counterSnapshotPublisher);
105 | counterSnapshotSubscriber = null;
106 | }
107 | }
108 | else
109 | {
110 | counterSnapshotSubscriber = null;
111 | }
112 | if (context.transport() == Transport.LOCAL || context.mode() == Mode.PUBLISHER)
113 | {
114 | countersPoller = new CountersPoller[context.monitoringLocations().size()];
115 | final List monitoringLocations = context.monitoringLocations();
116 | for (int i = 0; i < monitoringLocations.size(); i++)
117 | {
118 | final MonitoringLocation monitoringLocation = monitoringLocations.get(i);
119 | final CounterEventHandler counterEventHandler = new CounterEventHandler(
120 | new CounterRepository<>(PublisherCounterSet::new),
121 | new CounterRepository<>(SubscriberCounterSet::new),
122 | context.counterSnapshotListener(), epochClock);
123 | countersPoller[i] = new CountersPoller(
124 | counterEventHandler, monitoringLocation.label,
125 | monitoringLocation.aeronDirectoryName, epochClock);
126 | }
127 | }
128 | else
129 | {
130 | countersPoller = new CountersPoller[0];
131 | }
132 | }
133 |
134 | /**
135 | * Main method to start an Aether agent.
136 | *
137 | * @param args configuration files
138 | */
139 | public static void main(final String[] args)
140 | {
141 | SystemUtil.loadPropertiesFiles(args);
142 | try (Aether aether = launch(new Context()))
143 | {
144 | new ShutdownSignalBarrier().await();
145 | }
146 | }
147 |
148 | /**
149 | * Launches an Aether agent with the provided configuration context.
150 | *
151 | * @param context configuration context
152 | * @return the Aether instance
153 | */
154 | public static Aether launch(final Context context)
155 | {
156 | return new Aether(context);
157 | }
158 |
159 | /**
160 | * {@inheritDoc}
161 | */
162 | @Override
163 | public int doWork()
164 | {
165 | int work = 0;
166 | for (final CountersPoller poller : countersPoller)
167 | {
168 | work += poller.doWork();
169 | }
170 | if (counterSnapshotSubscriber != null)
171 | {
172 | work += counterSnapshotSubscriber.doWork();
173 | }
174 | return aeronClient.conductorAgentInvoker().invoke() + work;
175 | }
176 |
177 | /**
178 | * {@inheritDoc}
179 | */
180 | @Override
181 | public String roleName()
182 | {
183 | return "aether";
184 | }
185 |
186 | /**
187 | * {@inheritDoc}
188 | */
189 | @Override
190 | public void close()
191 | {
192 | CloseHelper.quietClose(counterSnapshotSubscriber);
193 | CloseHelper.quietClose(counterSnapshotPublisher);
194 | CloseHelper.quietClose(agentRunner);
195 | CloseHelper.quietClose(aeronClient);
196 | CloseHelper.quietClose(mediaDriver);
197 | }
198 |
199 | /**
200 | * Configuration context.
201 | */
202 | public static final class Context
203 | {
204 | private CounterSnapshotListener counterSnapshotListener = new ConsolePrinter();
205 | private List monitoringLocations = null;
206 | private ThreadingMode threadingMode = ThreadingMode.THREADED;
207 | private Transport transport = Configuration.transport();
208 | private boolean launchEmbeddedMediaDriver = true;
209 | private String aeronDirectoryName = CommonContext.getAeronDirectoryName();
210 | private Mode mode = Configuration.mode();
211 |
212 | void validate()
213 | {
214 | if (transport == Transport.AERON)
215 | {
216 | if (mode == Mode.LOCAL)
217 | {
218 | throw new IllegalStateException(
219 | "Must specify either PUBLISHER or SUBSCRIBER mode when using AERON transport");
220 | }
221 | }
222 | else if (mode != Mode.LOCAL)
223 | {
224 | throw new IllegalStateException("Mode must be LOCAL if transport is LOCAL");
225 | }
226 | }
227 |
228 | public Context mode(final Mode mode)
229 | {
230 | this.mode = mode;
231 | return this;
232 | }
233 |
234 | public Mode mode()
235 | {
236 | return mode;
237 | }
238 |
239 | public Context transport(final Transport transport)
240 | {
241 | this.transport = transport;
242 | return this;
243 | }
244 |
245 | public Transport transport()
246 | {
247 | return transport;
248 | }
249 |
250 | public Context counterSnapshotListener(final CounterSnapshotListener counterSnapshotListener)
251 | {
252 | this.counterSnapshotListener = counterSnapshotListener;
253 | return this;
254 | }
255 |
256 | public CounterSnapshotListener counterSnapshotListener()
257 | {
258 | return counterSnapshotListener;
259 | }
260 |
261 | public Context monitoringLocations(final List monitoringLocations)
262 | {
263 | this.monitoringLocations = monitoringLocations;
264 | return this;
265 | }
266 |
267 | public List monitoringLocations()
268 | {
269 | if (monitoringLocations == null)
270 | {
271 | monitoringLocations = new ArrayList<>();
272 | final String spec = Configuration.monitoringLocations();
273 | if (spec.length() != 0)
274 | {
275 | final String[] instances = spec.split(";");
276 | for (final String instance : instances)
277 | {
278 | monitoringLocations.add(new MonitoringLocation(
279 | instance.substring(0, instance.indexOf(':')),
280 | instance.substring(instance.indexOf(':') + 1)));
281 | }
282 | }
283 | }
284 |
285 | return monitoringLocations;
286 | }
287 |
288 | public Context threadingMode(final ThreadingMode threadingMode)
289 | {
290 | this.threadingMode = threadingMode;
291 | return this;
292 | }
293 |
294 | public ThreadingMode threadingMode()
295 | {
296 | return threadingMode;
297 | }
298 |
299 | public Context launchEmbeddedMediaDriver(final boolean launchEmbeddedMediaDriver)
300 | {
301 | this.launchEmbeddedMediaDriver = launchEmbeddedMediaDriver;
302 | return this;
303 | }
304 |
305 | public boolean launchEmbeddedMediaDriver()
306 | {
307 | return launchEmbeddedMediaDriver;
308 | }
309 |
310 | public Context aeronDirectoryName(final String aeronDirectoryName)
311 | {
312 | this.aeronDirectoryName = aeronDirectoryName;
313 | return this;
314 | }
315 |
316 | public String aeronDirectoryName()
317 | {
318 | return aeronDirectoryName;
319 | }
320 | }
321 |
322 | public static final class MonitoringLocation
323 | {
324 | private final String label;
325 | private final String aeronDirectoryName;
326 |
327 | public MonitoringLocation(final String label, final String aeronDirectoryName)
328 | {
329 | this.aeronDirectoryName = aeronDirectoryName;
330 | this.label = label;
331 | }
332 | }
333 |
334 | public static final class Configuration
335 | {
336 | public static final String MONITORING_LOCATIONS_PROPERTY_NAME = "aether.monitoringLocations";
337 | public static final String TRANSPORT_PROPERTY_NAME = "aether.transport";
338 | public static final String MODE_PROPERTY_NAME = "aether.mode";
339 |
340 | public static String monitoringLocations()
341 | {
342 | return System.getProperty(MONITORING_LOCATIONS_PROPERTY_NAME,
343 | "default:" + CommonContext.getAeronDirectoryName());
344 | }
345 |
346 | public static Transport transport()
347 | {
348 | return Optional.ofNullable(System.getProperty(TRANSPORT_PROPERTY_NAME))
349 | .map(Transport::valueOf).orElse(Transport.LOCAL);
350 | }
351 |
352 | public static Mode mode()
353 | {
354 | return Optional.ofNullable(System.getProperty(MODE_PROPERTY_NAME))
355 | .map(Mode::valueOf).orElse(Mode.LOCAL);
356 | }
357 | }
358 |
359 | public enum ThreadingMode
360 | {
361 | THREADED,
362 | INVOKER
363 | }
364 |
365 | public enum Transport
366 | {
367 | LOCAL,
368 | AERON
369 | }
370 |
371 | public enum Mode
372 | {
373 | LOCAL,
374 | PUBLISHER,
375 | SUBSCRIBER
376 | }
377 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/CountersPoller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether;
19 |
20 | import static io.aeron.CncFileDescriptor.CNC_FILE;
21 | import static io.aeron.CncFileDescriptor.CNC_VERSION;
22 | import static io.aeron.CncFileDescriptor.cncVersionOffset;
23 | import static io.aeron.CncFileDescriptor.createCountersMetaDataBuffer;
24 | import static io.aeron.CncFileDescriptor.createCountersValuesBuffer;
25 | import static io.aeron.CncFileDescriptor.createMetaDataBuffer;
26 |
27 | import java.io.File;
28 | import java.io.IOException;
29 | import java.io.UncheckedIOException;
30 | import java.nio.MappedByteBuffer;
31 | import java.nio.channels.FileChannel;
32 | import java.nio.file.StandardOpenOption;
33 |
34 | import com.aitusoftware.aether.event.CounterValueListener;
35 |
36 | import org.agrona.DirectBuffer;
37 | import org.agrona.concurrent.Agent;
38 | import org.agrona.concurrent.EpochClock;
39 | import org.agrona.concurrent.status.CountersReader;
40 |
41 | import io.aeron.driver.status.PublisherLimit;
42 | import io.aeron.driver.status.PublisherPos;
43 | import io.aeron.driver.status.ReceiverHwm;
44 | import io.aeron.driver.status.ReceiverPos;
45 | import io.aeron.driver.status.SenderBpe;
46 | import io.aeron.driver.status.SenderLimit;
47 | import io.aeron.driver.status.SenderPos;
48 | import io.aeron.driver.status.SubscriberPos;
49 | import io.aeron.driver.status.SystemCounterDescriptor;
50 |
51 | final class CountersPoller implements Agent, CountersReader.MetaData
52 | {
53 | private static final long POLL_INTERVAL_MS = 1_000;
54 | private final CounterValueListener counterValueListener;
55 | private final CountersReader countersReader;
56 | private final StringBuilder labelBuffer = new StringBuilder();
57 | private final String label;
58 | private final EpochClock epochClock;
59 | private long lastPollMs = 0L;
60 | private int countersRead;
61 |
62 | CountersPoller(
63 | final CounterValueListener counterValueListener,
64 | final String label, final String aeronDirectoryName,
65 | final EpochClock epochClock)
66 | {
67 | this.counterValueListener = counterValueListener;
68 | this.label = label;
69 | this.epochClock = epochClock;
70 | final File cncFile = new File(aeronDirectoryName, CNC_FILE);
71 |
72 | final MappedByteBuffer cncByteBuffer = mapExistingFileReadOnly(cncFile);
73 | final DirectBuffer cncMetaData = createMetaDataBuffer(cncByteBuffer);
74 | final int cncVersion = cncMetaData.getInt(cncVersionOffset(0));
75 |
76 | if (CNC_VERSION != cncVersion)
77 | {
78 | throw new IllegalStateException("CnC version not supported: file version=" + cncVersion);
79 | }
80 |
81 | countersReader = new CountersReader(
82 | createCountersMetaDataBuffer(cncByteBuffer, cncMetaData),
83 | createCountersValuesBuffer(cncByteBuffer, cncMetaData));
84 | }
85 |
86 | @Override
87 | public int doWork()
88 | {
89 | countersRead = 0;
90 | if (epochClock.time() > lastPollMs + POLL_INTERVAL_MS)
91 | {
92 | lastPollMs = epochClock.time();
93 | countersReader.forEach(this);
94 | counterValueListener.onEndOfBatch(label);
95 | }
96 | return countersRead;
97 | }
98 |
99 | @Override
100 | public void accept(final int counterId, final int typeId, final DirectBuffer keyBuffer, final String label)
101 | {
102 | countersRead++;
103 | labelBuffer.setLength(0);
104 | int streamId = -1;
105 | int sessionId = -1;
106 | long registrationId = -1;
107 | boolean isMonitoredType = isMonitoredSystemCounter(counterId, typeId);
108 | final String[] tokens = label.split(" ");
109 | switch (typeId)
110 | {
111 | // publisher counters
112 | case SenderLimit.SENDER_LIMIT_TYPE_ID:
113 | case SenderPos.SENDER_POSITION_TYPE_ID:
114 | case PublisherLimit.PUBLISHER_LIMIT_TYPE_ID:
115 | case SenderBpe.SENDER_BPE_TYPE_ID:
116 | case ReceiverHwm.RECEIVER_HWM_TYPE_ID:
117 | case ReceiverPos.RECEIVER_POS_TYPE_ID:
118 | case SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID:
119 | registrationId = Long.parseLong(tokens[1]);
120 | sessionId = Integer.parseInt(tokens[2]);
121 | streamId = Integer.parseInt(tokens[3]);
122 | labelBuffer.append(tokens[4]);
123 | isMonitoredType = true;
124 | break;
125 | case PublisherPos.PUBLISHER_POS_TYPE_ID:
126 | registrationId = Long.parseLong(tokens[2]);
127 | sessionId = Integer.parseInt(tokens[3]);
128 | streamId = Integer.parseInt(tokens[4]);
129 | labelBuffer.append(tokens[5]);
130 | isMonitoredType = true;
131 | break;
132 | }
133 | if (isMonitoredType)
134 | {
135 | final long value = countersReader.getCounterValue(counterId);
136 | counterValueListener.onCounterEvent(
137 | counterId, typeId, labelBuffer, sessionId, streamId, registrationId, value);
138 | }
139 | }
140 |
141 | private static boolean isMonitoredSystemCounter(final int counterId, final int typeId)
142 | {
143 | return typeId == SystemCounterDescriptor.SYSTEM_COUNTER_TYPE_ID &&
144 | (
145 | counterId == SystemCounterDescriptor.BYTES_SENT.id() ||
146 | counterId == SystemCounterDescriptor.BYTES_RECEIVED.id() ||
147 | counterId == SystemCounterDescriptor.NAK_MESSAGES_SENT.id() ||
148 | counterId == SystemCounterDescriptor.NAK_MESSAGES_RECEIVED.id() ||
149 | counterId == SystemCounterDescriptor.ERRORS.id() ||
150 | counterId == SystemCounterDescriptor.CLIENT_TIMEOUTS.id());
151 | }
152 |
153 | @Override
154 | public String roleName()
155 | {
156 | return "counters-poller";
157 | }
158 |
159 | private MappedByteBuffer mapExistingFileReadOnly(final File cncFile)
160 | {
161 | if (!cncFile.exists())
162 | {
163 | throw new IllegalStateException("File does not exist: " + cncFile);
164 | }
165 | try
166 | {
167 |
168 | return FileChannel.open(cncFile.toPath(), StandardOpenOption.READ)
169 | .map(FileChannel.MapMode.READ_ONLY, 0, cncFile.length());
170 | }
171 | catch (final IOException e)
172 | {
173 | throw new UncheckedIOException(e);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/aggregation/RateBucket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.aggregation;
19 |
20 | import java.util.concurrent.TimeUnit;
21 |
22 | public final class RateBucket
23 | {
24 | private final long duration;
25 | private final TimeUnit durationUnit;
26 |
27 | public RateBucket(final long duration, final TimeUnit durationUnit)
28 | {
29 | this.duration = duration;
30 | this.durationUnit = durationUnit;
31 | }
32 |
33 | public long getDuration()
34 | {
35 | return duration;
36 | }
37 |
38 | public TimeUnit getDurationUnit()
39 | {
40 | return durationUnit;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/aggregation/RateConsumer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.aggregation;
19 |
20 | import java.util.concurrent.TimeUnit;
21 |
22 | public interface RateConsumer
23 | {
24 | void onAggregateRate(long duration, TimeUnit durationUnit, long bytesPerSecond);
25 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/aggregation/StreamRate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.aggregation;
19 |
20 | import org.agrona.collections.Long2ObjectHashMap;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | import static java.util.Comparator.comparingLong;
27 |
28 | public final class StreamRate
29 | {
30 | @SuppressWarnings("unchecked")
31 | private final Long2ObjectHashMap[] rollingWindowsByTimeUnit =
32 | new Long2ObjectHashMap[TimeUnit.values().length];
33 |
34 | public StreamRate(final List rateBuckets)
35 | {
36 | final List copy = new ArrayList<>(rateBuckets);
37 | copy.sort(comparingLong(bucket -> bucket.getDurationUnit().toNanos(bucket.getDuration())));
38 |
39 | for (final RateBucket rateBucket : copy)
40 | {
41 | if (rollingWindowsByTimeUnit[rateBucket.getDurationUnit().ordinal()] == null)
42 | {
43 | rollingWindowsByTimeUnit[rateBucket.getDurationUnit().ordinal()] = new Long2ObjectHashMap<>();
44 | }
45 |
46 | final Long2ObjectHashMap rollingWindows =
47 | rollingWindowsByTimeUnit[rateBucket.getDurationUnit().ordinal()];
48 | if (rollingWindows.containsKey(rateBucket.getDuration()))
49 | {
50 | throw new IllegalArgumentException("Bucket already defined: " +
51 | rateBucket.getDuration() + " " + rateBucket.getDurationUnit());
52 | }
53 |
54 | rollingWindows.put(rateBucket.getDuration(),
55 | new RollingWindow((int)rateBucket.getDurationUnit().toSeconds(rateBucket.getDuration()),
56 | rateBucket.getDurationUnit()));
57 | }
58 | }
59 |
60 | public void streamPosition(final long epochMillis, final long position)
61 | {
62 | for (int i = 0; i < rollingWindowsByTimeUnit.length; i++)
63 | {
64 | final Long2ObjectHashMap rollingWindowsByDuration = rollingWindowsByTimeUnit[i];
65 | if (rollingWindowsByDuration != null)
66 | {
67 | final Long2ObjectHashMap.KeyIterator keys = rollingWindowsByDuration.keySet().iterator();
68 | while (keys.hasNext())
69 | {
70 | final long duration = keys.nextLong();
71 | rollingWindowsByDuration.get(duration).updateBytePosition(epochMillis, position);
72 | }
73 | }
74 | }
75 | }
76 |
77 | public void consumeRates(final RateConsumer rateConsumer)
78 | {
79 | for (int i = 0; i < rollingWindowsByTimeUnit.length; i++)
80 | {
81 | final Long2ObjectHashMap rollingWindowsByDuration = rollingWindowsByTimeUnit[i];
82 | if (rollingWindowsByDuration != null)
83 | {
84 | final Long2ObjectHashMap.KeyIterator keys = rollingWindowsByDuration.keySet().iterator();
85 | while (keys.hasNext())
86 | {
87 | final long duration = keys.nextLong();
88 | final RollingWindow rollingWindow = rollingWindowsByDuration.get(duration);
89 | final long averageValue = rollingWindow.getAverageValue();
90 | rateConsumer.onAggregateRate(duration, rollingWindow.durationUnit, averageValue);
91 | }
92 | }
93 | }
94 | }
95 |
96 | private static final class RollingWindow
97 | {
98 | private final long[] segmentValues;
99 | private final TimeUnit durationUnit;
100 | private long lastUpdate;
101 | private int pointer = 0;
102 |
103 | RollingWindow(final int numberOfSegments, final TimeUnit durationUnit)
104 | {
105 | segmentValues = new long[numberOfSegments];
106 | this.durationUnit = durationUnit;
107 | }
108 |
109 | void updateBytePosition(final long epochMillis, final long bytePosition)
110 | {
111 | if (epochMillis >= lastUpdate + TimeUnit.SECONDS.toMillis(1))
112 | {
113 | segmentValues[pointer % segmentValues.length] = bytePosition;
114 | pointer++;
115 | lastUpdate = epochMillis;
116 | }
117 | }
118 |
119 | long getAverageValue()
120 | {
121 | final int endPointer = pointer - 1;
122 | final int startPointer = Math.max(0, pointer - segmentValues.length);
123 |
124 | long accumulator = 0L;
125 | int count = 0;
126 | for (int i = startPointer; i < endPointer; i++)
127 | {
128 | final long segmentDelta = segmentValues[(i + 1) % segmentValues.length] -
129 | segmentValues[i % segmentValues.length];
130 | accumulator += segmentDelta;
131 | count++;
132 | }
133 | if (count == 0)
134 | {
135 | return 0;
136 | }
137 | return accumulator / count;
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/annotation/CallerOwned.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.annotation;
19 |
20 | import java.lang.annotation.ElementType;
21 | import java.lang.annotation.Retention;
22 | import java.lang.annotation.RetentionPolicy;
23 | import java.lang.annotation.Target;
24 |
25 | /**
26 | * Marker interface to show that a parameter is owned by the caller of the method.
27 | */
28 | @Retention(RetentionPolicy.SOURCE)
29 | @Target(ElementType.PARAMETER)
30 | public @interface CallerOwned
31 | {
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/CharSequenceUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | final class CharSequenceUtil
21 | {
22 | private CharSequenceUtil()
23 | {
24 | }
25 |
26 | static boolean charSequencesEqual(final CharSequence a, final CharSequence b)
27 | {
28 | if (a.length() != b.length())
29 | {
30 | return false;
31 | }
32 |
33 | for (int i = 0; i < a.length(); i++)
34 | {
35 | if (a.charAt(i) != b.charAt(i))
36 | {
37 | return false;
38 | }
39 | }
40 |
41 | return true;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/ConsolePrinter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.Set;
23 |
24 | import com.aitusoftware.aether.annotation.CallerOwned;
25 | import com.aitusoftware.aether.model.ChannelSessionKey;
26 | import com.aitusoftware.aether.model.PublisherCounterSet;
27 | import com.aitusoftware.aether.model.SubscriberCounterSet;
28 | import com.aitusoftware.aether.model.SystemCounters;
29 |
30 | import org.agrona.collections.Long2LongHashMap;
31 |
32 | public final class ConsolePrinter implements CounterSnapshotListener
33 | {
34 | private final SystemSnapshot systemSnapshot = new SystemSnapshot();
35 |
36 | @Override
37 | public void onSnapshot(
38 | final String label,
39 | final long timestamp,
40 | @CallerOwned final List publisherCounters,
41 | @CallerOwned final List subscriberCounters,
42 | @CallerOwned final SystemCounters systemCounters)
43 | {
44 | systemSnapshot.onSnapshot(label, timestamp, publisherCounters, subscriberCounters, systemCounters);
45 | systemSnapshot.getSystemCounters().forEach((contextLabel, counters) ->
46 | {
47 | System.out.printf("===== System counters for \"%s\" =====%n", contextLabel);
48 | System.out.printf("Bytes sent: %20d%n", counters.bytesSent());
49 | System.out.printf("Bytes received: %20d%n", counters.bytesReceived());
50 | System.out.printf("NAKs sent: %20d%n", counters.naksSent());
51 | System.out.printf("NAKs received: %20d%n", counters.naksReceived());
52 | System.out.printf("Errors: %20d%n", counters.errors());
53 | System.out.printf("Client timeouts: %20d%n", counters.clientTimeouts());
54 | });
55 | final Map>> connectionsByStream =
56 | systemSnapshot.getConnectionsByStream();
57 | System.out.printf("===== Monitoring %d channels =====%n", connectionsByStream.size());
58 | for (final StreamKey streamKey : connectionsByStream.keySet())
59 | {
60 | System.out.printf("==== %s/%d ====%n", streamKey.getChannel(), streamKey.getStreamId());
61 | final Set>> sources =
62 | connectionsByStream.get(streamKey).entrySet();
63 | for (final Map.Entry> source : sources)
64 | {
65 | final ChannelSessionKey publisherKey = source.getKey();
66 | final PublisherCounterSet publisher = systemSnapshot.getPublisherCounterSet(publisherKey);
67 | System.out.printf("%n---- Publisher Session %d ----%n", publisher.sessionId());
68 | System.out.printf("| publisher position: %20d%n", publisher.publisherPosition());
69 | System.out.printf("| publisher limit: %20d%n", publisher.publisherLimit());
70 | System.out.printf("| sender position: %20d%n", publisher.senderPosition());
71 | System.out.printf("| sender limit: %20d%n", publisher.senderLimit());
72 | final Set subscribers = source.getValue();
73 | for (final ChannelSessionKey subscriberKey : subscribers)
74 | {
75 | final SubscriberCounterSet subscriber =
76 | systemSnapshot.getSubscriberCounterSet(subscriberKey);
77 | System.out.printf("---- Subscriber ----%n");
78 | System.out.printf("| receiver position: %20d%n", subscriber.receiverPosition());
79 | System.out.printf("| receiver HWM: %20d%n", subscriber.receiverHighWaterMark());
80 | final Long2LongHashMap subscriberPositions = subscriber.subscriberPositions();
81 | subscriberPositions.forEach((reg, pos) ->
82 | {
83 | System.out.printf("| position (%d): %20d%n", reg, pos);
84 | });
85 | }
86 | System.out.printf("-----------------------------------------%n");
87 | }
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/CounterEventHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import com.aitusoftware.aether.annotation.CallerOwned;
24 | import com.aitusoftware.aether.model.SystemCounters;
25 | import com.aitusoftware.aether.model.PublisherCounterSet;
26 | import com.aitusoftware.aether.model.SubscriberCounterSet;
27 |
28 | import org.agrona.concurrent.EpochClock;
29 |
30 | import io.aeron.driver.status.PublisherLimit;
31 | import io.aeron.driver.status.PublisherPos;
32 | import io.aeron.driver.status.ReceiverHwm;
33 | import io.aeron.driver.status.ReceiverPos;
34 | import io.aeron.driver.status.SenderBpe;
35 | import io.aeron.driver.status.SenderLimit;
36 | import io.aeron.driver.status.SenderPos;
37 | import io.aeron.driver.status.SubscriberPos;
38 | import io.aeron.driver.status.SystemCounterDescriptor;
39 |
40 | public final class CounterEventHandler implements CounterValueListener
41 | {
42 | private final CounterRepository publisherCounterRepository;
43 | private final CounterRepository subscriberCounterRepository;
44 | private final CounterSnapshotListener counterSnapshotListener;
45 | private final EpochClock epochClock;
46 | private final List publisherCounters = new ArrayList<>();
47 | private final List subscriberCounters = new ArrayList<>();
48 | private final StringBuilder strippedChannel = new StringBuilder();
49 | private final SystemCounters systemCounters = new SystemCounters();
50 |
51 | public CounterEventHandler(
52 | final CounterRepository publisherCounterRepository,
53 | final CounterRepository subscriberCounterRepository,
54 | final CounterSnapshotListener counterSnapshotListener,
55 | final EpochClock epochClock)
56 | {
57 | this.publisherCounterRepository = publisherCounterRepository;
58 | this.subscriberCounterRepository = subscriberCounterRepository;
59 | this.counterSnapshotListener = counterSnapshotListener;
60 | this.epochClock = epochClock;
61 | }
62 |
63 | @Override
64 | public void onCounterEvent(
65 | final int counterId, final int counterTypeId, @CallerOwned final CharSequence channel,
66 | final int sessionId, final int streamId, final long registrationId, final long value)
67 | {
68 | strippedChannel.setLength(0);
69 | for (int i = 0; i < channel.length(); i++)
70 | {
71 | if (channel.charAt(i) == '|')
72 | {
73 | break;
74 | }
75 | strippedChannel.append(channel.charAt(i));
76 | }
77 | if (strippedChannel.indexOf("aeron:ipc") == 0)
78 | {
79 | strippedChannel.setLength("aeron:ipc".length());
80 | }
81 | switch (counterTypeId)
82 | {
83 | // publisher counters
84 | case SenderLimit.SENDER_LIMIT_TYPE_ID:
85 | getPublisherCounters(strippedChannel, sessionId, streamId).senderLimit(value);
86 | break;
87 | case SenderPos.SENDER_POSITION_TYPE_ID:
88 | getPublisherCounters(strippedChannel, sessionId, streamId).senderPosition(value);
89 | break;
90 | case PublisherPos.PUBLISHER_POS_TYPE_ID:
91 | getPublisherCounters(strippedChannel, sessionId, streamId).publisherPosition(value);
92 | break;
93 | case PublisherLimit.PUBLISHER_LIMIT_TYPE_ID:
94 | getPublisherCounters(strippedChannel, sessionId, streamId).publisherLimit(value);
95 | break;
96 | case SenderBpe.SENDER_BPE_TYPE_ID:
97 | getPublisherCounters(strippedChannel, sessionId, streamId).backPressureEvents(value);
98 | break;
99 | // subscriber counters
100 | case ReceiverHwm.RECEIVER_HWM_TYPE_ID:
101 | getSubscriberCounters(strippedChannel, sessionId, streamId)
102 | .receiverHighWaterMark(value);
103 | break;
104 | case ReceiverPos.RECEIVER_POS_TYPE_ID:
105 | getSubscriberCounters(strippedChannel, sessionId, streamId).receiverPosition(value);
106 | break;
107 | case SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID:
108 | getSubscriberCounters(strippedChannel, sessionId, streamId)
109 | .subscriberPosition(registrationId, value);
110 | break;
111 | //system counters
112 | case SystemCounterDescriptor.SYSTEM_COUNTER_TYPE_ID:
113 | final SystemCounterDescriptor descriptor = SystemCounterDescriptor.get(counterId);
114 | switch (descriptor)
115 | {
116 | case BYTES_SENT:
117 | systemCounters.bytesSent(value);
118 | break;
119 | case BYTES_RECEIVED:
120 | systemCounters.bytesReceived(value);
121 | break;
122 | case NAK_MESSAGES_SENT:
123 | systemCounters.naksSent(value);
124 | break;
125 | case NAK_MESSAGES_RECEIVED:
126 | systemCounters.naksReceived(value);
127 | break;
128 | case ERRORS:
129 | systemCounters.errors(value);
130 | break;
131 | case CLIENT_TIMEOUTS:
132 | systemCounters.clientTimeouts(value);
133 | break;
134 |
135 | }
136 | break;
137 | }
138 | }
139 |
140 | @Override
141 | public void onEndOfBatch(final String label)
142 | {
143 | publisherCounters.clear();
144 | subscriberCounters.clear();
145 | publisherCounterRepository.forEach(publisherCounters::add);
146 | subscriberCounterRepository.forEach(subscriberCounters::add);
147 |
148 | counterSnapshotListener.onSnapshot(label, epochClock.time(),
149 | publisherCounters, subscriberCounters, systemCounters);
150 | }
151 |
152 | private PublisherCounterSet getPublisherCounters(
153 | final CharSequence channel, final int sessionId, final int streamId)
154 | {
155 | return publisherCounterRepository.getOrCreate(channel, sessionId, streamId);
156 | }
157 |
158 | private SubscriberCounterSet getSubscriberCounters(
159 | final CharSequence channel, final int sessionId, final int streamId)
160 | {
161 | return subscriberCounterRepository.getOrCreate(channel, sessionId, streamId);
162 | }
163 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/CounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.HashMap;
21 | import java.util.Map;
22 | import java.util.function.Consumer;
23 | import java.util.function.Supplier;
24 |
25 | import com.aitusoftware.aether.model.SessionKeyed;
26 |
27 | public final class CounterRepository
28 | {
29 | private final Map countersMap = new HashMap<>();
30 | private final Supplier factory;
31 |
32 | public CounterRepository(final Supplier factory)
33 | {
34 | this.factory = factory;
35 | }
36 |
37 | T getOrCreate(
38 | final CharSequence channel, final int sessionId, final int streamId)
39 | {
40 | final Key key = new Key().set(channel, sessionId, streamId);
41 | T counters = countersMap.get(key);
42 | if (counters == null)
43 | {
44 | counters = factory.get();
45 | counters.reset(channel, sessionId, streamId);
46 | countersMap.put(key, counters);
47 | }
48 | return counters;
49 | }
50 |
51 | void forEach(final Consumer consumer)
52 | {
53 | countersMap.values().forEach(consumer);
54 | }
55 |
56 | private static final class Key
57 | {
58 | private StringBuilder channel = new StringBuilder();
59 | private int sessionId;
60 | private int streamId;
61 |
62 | Key set(final CharSequence channel, final int sessionId, final int streamId)
63 | {
64 | this.channel.setLength(0);
65 | this.channel.append(channel);
66 | this.sessionId = sessionId;
67 | this.streamId = streamId;
68 |
69 | return this;
70 | }
71 |
72 | @Override
73 | public boolean equals(final Object o)
74 | {
75 | if (this == o)
76 | {
77 | return true;
78 | }
79 | if (o == null || getClass() != o.getClass())
80 | {
81 | return false;
82 | }
83 | final Key key = (Key)o;
84 | return sessionId == key.sessionId &&
85 | streamId == key.streamId &&
86 | CharSequenceUtil.charSequencesEqual(channel, key.channel);
87 | }
88 |
89 | @Override
90 | public int hashCode()
91 | {
92 | int hashCode = hashCharSequence(channel);
93 | hashCode = 31 * hashCode + sessionId;
94 | hashCode = 31 * hashCode + streamId;
95 | return hashCode;
96 | }
97 |
98 | private int hashCharSequence(final CharSequence charSequence)
99 | {
100 | int hashCode = 0;
101 | for (int i = 0; i < charSequence.length(); i++)
102 | {
103 | hashCode = 31 * hashCode + charSequence.charAt(i);
104 | }
105 |
106 | return hashCode;
107 | }
108 |
109 | }
110 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/CounterSnapshotListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.List;
21 |
22 | import com.aitusoftware.aether.annotation.CallerOwned;
23 | import com.aitusoftware.aether.model.SystemCounters;
24 | import com.aitusoftware.aether.model.PublisherCounterSet;
25 | import com.aitusoftware.aether.model.SubscriberCounterSet;
26 |
27 | /**
28 | * Defines a listener to snapshots of a MediaDriver's counters.
29 | */
30 | public interface CounterSnapshotListener
31 | {
32 | /**
33 | * Called when a complete snapshot of the counters has been built.
34 | *
35 | * @param label label of the MediaDriver
36 | * @param timestamp timestamp of the snapshot
37 | * @param publisherCounters counters describing publisher state
38 | * @param subscriberCounters counters describing subscriber state
39 | * @param systemCounters counters describing system state
40 | */
41 | void onSnapshot(
42 | String label,
43 | long timestamp,
44 | @CallerOwned List publisherCounters,
45 | @CallerOwned List subscriberCounters,
46 | @CallerOwned SystemCounters systemCounters);
47 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/CounterValueListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import com.aitusoftware.aether.annotation.CallerOwned;
21 |
22 | public interface CounterValueListener
23 | {
24 | void onCounterEvent(
25 | int counterId, int counterTypeId,
26 | @CallerOwned CharSequence channel, int sessionId,
27 | int streamId, long registrationId, long value);
28 |
29 | void onEndOfBatch(String label);
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/RateMonitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import com.aitusoftware.aether.aggregation.RateBucket;
21 | import com.aitusoftware.aether.aggregation.StreamRate;
22 | import com.aitusoftware.aether.annotation.CallerOwned;
23 | import com.aitusoftware.aether.model.ChannelSessionKey;
24 | import com.aitusoftware.aether.model.PublisherCounterSet;
25 | import com.aitusoftware.aether.model.SubscriberCounterSet;
26 | import com.aitusoftware.aether.model.SystemCounters;
27 |
28 | import java.util.HashMap;
29 | import java.util.List;
30 | import java.util.Map;
31 |
32 | public final class RateMonitor implements CounterSnapshotListener
33 | {
34 | private final List rateBuckets;
35 | private final HashMap streamRateByPublisher = new HashMap<>();
36 |
37 | public RateMonitor(final List rateBuckets)
38 | {
39 | this.rateBuckets = rateBuckets;
40 | }
41 |
42 | @Override
43 | public void onSnapshot(
44 | final String label,
45 | final long timestamp,
46 | @CallerOwned final List publisherCounters,
47 | @CallerOwned final List subscriberCounters,
48 | @CallerOwned final SystemCounters systemCounters)
49 | {
50 | for (int i = 0; i < publisherCounters.size(); i++)
51 | {
52 | final PublisherCounterSet publisherCounter = publisherCounters.get(i);
53 | final ChannelSessionKey streamKey = new ChannelSessionKey(label, publisherCounter.channel().toString(),
54 | publisherCounter.streamId(), publisherCounter.sessionId());
55 | StreamRate streamRate = streamRateByPublisher.get(streamKey);
56 | if (streamRate == null)
57 | {
58 | streamRate = new StreamRate(rateBuckets);
59 | streamRateByPublisher.put(streamKey, streamRate);
60 | }
61 |
62 | streamRate.streamPosition(timestamp, publisherCounter.publisherPosition());
63 | }
64 | }
65 |
66 | public Map publisherRates()
67 | {
68 | return streamRateByPublisher;
69 | }
70 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/StreamKey.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.Objects;
21 |
22 | /**
23 | * Represents a single data stream, identified by channel and streamId.
24 | */
25 | public final class StreamKey
26 | {
27 | private final String channel;
28 | private final int streamId;
29 |
30 | StreamKey(final String channel, final int streamId)
31 | {
32 | this.channel = channel;
33 | this.streamId = streamId;
34 | }
35 |
36 | /**
37 | * Returns the channel.
38 | *
39 | * @return the channel
40 | */
41 | public String getChannel()
42 | {
43 | return channel;
44 | }
45 |
46 | /**
47 | * Returns the streamId.
48 | *
49 | * @return the streamId
50 | */
51 | public int getStreamId()
52 | {
53 | return streamId;
54 | }
55 |
56 | @Override
57 | public boolean equals(final Object o)
58 | {
59 | if (this == o)
60 | {
61 | return true;
62 | }
63 | if (o == null || getClass() != o.getClass())
64 | {
65 | return false;
66 | }
67 | final StreamKey streamKey = (StreamKey)o;
68 | return streamId == streamKey.streamId &&
69 | Objects.equals(channel, streamKey.channel);
70 | }
71 |
72 | @Override
73 | public int hashCode()
74 | {
75 | return Objects.hash(channel, streamId);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/event/SystemSnapshot.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import java.util.HashMap;
21 | import java.util.HashSet;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Set;
25 |
26 | import com.aitusoftware.aether.annotation.CallerOwned;
27 | import com.aitusoftware.aether.model.ChannelSessionKey;
28 | import com.aitusoftware.aether.model.PublisherCounterSet;
29 | import com.aitusoftware.aether.model.SubscriberCounterSet;
30 | import com.aitusoftware.aether.model.SystemCounters;
31 |
32 | /**
33 | * Maintains an aggregate view over multiple MediaDriver snapshots.
34 | */
35 | public final class SystemSnapshot implements CounterSnapshotListener
36 | {
37 | private final Map> allSubscribers = new HashMap<>();
38 | private final Map>> connectionsByStream = new HashMap<>();
39 | private final Map publishersByRegistration = new HashMap<>();
40 | private final Map subscribersByRegistration = new HashMap<>();
41 | private final Map systemCountersByLabel = new HashMap<>();
42 |
43 | /**
44 | * {@inheritDoc}
45 | */
46 | @Override
47 | public void onSnapshot(
48 | final String label,
49 | final long timestamp,
50 | @CallerOwned final List publisherCounters,
51 | @CallerOwned final List subscriberCounters,
52 | @CallerOwned final SystemCounters systemCounters)
53 | {
54 | systemCounters.copyInto(systemCountersByLabel.computeIfAbsent(label, key -> new SystemCounters()));
55 | for (final SubscriberCounterSet subscriberCounter : subscriberCounters)
56 | {
57 | final StreamKey streamKey = new StreamKey(
58 | subscriberCounter.channel().toString(), subscriberCounter.streamId());
59 | allSubscribers.computeIfAbsent(streamKey, key -> new HashMap<>())
60 | .put(subscriberCounter.sessionId(), subscriberCounter.copy());
61 | subscribersByRegistration.put(
62 | new ChannelSessionKey(label, subscriberCounter.channel().toString(), subscriberCounter.streamId(),
63 | subscriberCounter.sessionId()), subscriberCounter.copy());
64 | }
65 |
66 | for (final PublisherCounterSet publisherCounter : publisherCounters)
67 | {
68 | publishersByRegistration.put(
69 | new ChannelSessionKey(label, publisherCounter.channel().toString(), publisherCounter.streamId(),
70 | publisherCounter.sessionId()), publisherCounter.copy());
71 |
72 | final StreamKey publisherStreamKey = new StreamKey(
73 | publisherCounter.channel().toString(), publisherCounter.streamId());
74 | final Map> streamPublishers =
75 | connectionsByStream.computeIfAbsent(publisherStreamKey, key -> new HashMap<>());
76 | final ChannelSessionKey publisherChannelSessionKey = new ChannelSessionKey(
77 | label, publisherCounter.channel().toString(), publisherCounter.streamId(),
78 | publisherCounter.sessionId()
79 | );
80 | final Set channelSessionKeys = streamPublishers
81 | .computeIfAbsent(publisherChannelSessionKey, key -> new HashSet<>());
82 |
83 | final Set> subscribers =
84 | subscribersByRegistration.entrySet();
85 | for (final Map.Entry subscriber : subscribers)
86 | {
87 | if (
88 | subscriber.getKey().getChannel().equals(publisherChannelSessionKey.getChannel()) &&
89 | subscriber.getKey().getStreamId() == publisherChannelSessionKey.getStreamId() &&
90 | subscriber.getKey().getSessionId() == publisherChannelSessionKey.getSessionId())
91 | {
92 | channelSessionKeys.add(subscriber.getKey());
93 | }
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * Returns system counters keyed by MediaDriver label.
100 | *
101 | * @return the system counters
102 | */
103 | public Map getSystemCounters()
104 | {
105 | return systemCountersByLabel;
106 | }
107 |
108 | /**
109 | * Returns a tree-like structure identifying participants in individual message flows.
110 | *
111 | * @return the structure of identifiers
112 | */
113 | public Map>> getConnectionsByStream()
114 | {
115 | return connectionsByStream;
116 | }
117 |
118 | /**
119 | * Returns the publisher counters associated with a particular publisher.
120 | *
121 | * @param channelSessionKey the identifier of the publisher
122 | * @return the publisher counters
123 | */
124 | public PublisherCounterSet getPublisherCounterSet(final ChannelSessionKey channelSessionKey)
125 | {
126 | return publishersByRegistration.get(channelSessionKey);
127 | }
128 |
129 | /**
130 | * Returns the subscriber counters associated with a particular subscriber.
131 | *
132 | * @param channelSessionKey the identifier of the subscriber
133 | * @return the subscriber counters
134 | */
135 | public SubscriberCounterSet getSubscriberCounterSet(final ChannelSessionKey channelSessionKey)
136 | {
137 | return subscribersByRegistration.get(channelSessionKey);
138 | }
139 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/ChannelSessionKey.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | import java.util.Objects;
21 |
22 | /**
23 | * Describes a participant in a message flow.
24 | */
25 | public final class ChannelSessionKey
26 | {
27 | private final String label;
28 | private final String channel;
29 | private final int streamId;
30 | private final int sessionId;
31 |
32 | public ChannelSessionKey(
33 | final String label,
34 | final String channel,
35 | final int streamId,
36 | final int sessionId)
37 | {
38 | this.label = label;
39 | this.channel = channel;
40 | this.streamId = streamId;
41 | this.sessionId = sessionId;
42 | }
43 |
44 | public String getLabel()
45 | {
46 | return label;
47 | }
48 |
49 | public String getChannel()
50 | {
51 | return channel;
52 | }
53 |
54 | public int getStreamId()
55 | {
56 | return streamId;
57 | }
58 |
59 | public int getSessionId()
60 | {
61 | return sessionId;
62 | }
63 |
64 | @Override
65 | public boolean equals(final Object o)
66 | {
67 | if (this == o)
68 | {
69 | return true;
70 | }
71 | if (o == null || getClass() != o.getClass())
72 | {
73 | return false;
74 | }
75 | final ChannelSessionKey that = (ChannelSessionKey)o;
76 | return streamId == that.streamId &&
77 | sessionId == that.sessionId &&
78 | Objects.equals(channel, that.channel) &&
79 | Objects.equals(label, that.label);
80 | }
81 |
82 | @Override
83 | public int hashCode()
84 | {
85 | return Objects.hash(label, channel, streamId, sessionId);
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/ChannelStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | public final class ChannelStatus
21 | {
22 | private ChannelType channelType;
23 | private int status;
24 | private String channel;
25 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/ChannelType.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | public enum ChannelType
21 | {
22 | SUBSCRIBER,
23 | PUBLISHER
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/PublisherCounterSet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | /**
21 | * Models counters associated with a publisher.
22 | */
23 | public final class PublisherCounterSet implements SessionKeyed
24 | {
25 | private final StringBuilder channel = new StringBuilder();
26 | private long publisherPosition;
27 | private long backPressureEvents;
28 | private long senderPosition;
29 | private long senderLimit;
30 | private long publisherLimit;
31 | private int sessionId;
32 | private int streamId;
33 |
34 | /**
35 | * {@inheritDoc}
36 | */
37 | @Override
38 | public void reset(final CharSequence channel, final int sessionId, final int streamId)
39 | {
40 | this.channel.setLength(0);
41 | this.channel.append(channel);
42 | this.streamId = streamId;
43 | this.sessionId = sessionId;
44 | }
45 |
46 | public void publisherPosition(final long publisherPosition)
47 | {
48 | this.publisherPosition = publisherPosition;
49 | }
50 |
51 | public void backPressureEvents(final long backPressureEvents)
52 | {
53 | this.backPressureEvents = backPressureEvents;
54 | }
55 |
56 | public void senderPosition(final long senderPosition)
57 | {
58 | this.senderPosition = senderPosition;
59 | }
60 |
61 | public void senderLimit(final long senderLimit)
62 | {
63 | this.senderLimit = senderLimit;
64 | }
65 |
66 | public void publisherLimit(final long publisherLimit)
67 | {
68 | this.publisherLimit = publisherLimit;
69 | }
70 |
71 | public void sessionId(final int sessionId)
72 | {
73 | this.sessionId = sessionId;
74 | }
75 |
76 | public long publisherPosition()
77 | {
78 | return publisherPosition;
79 | }
80 |
81 | public long backPressureEvents()
82 | {
83 | return backPressureEvents;
84 | }
85 |
86 | public long senderPosition()
87 | {
88 | return senderPosition;
89 | }
90 |
91 | public long senderLimit()
92 | {
93 | return senderLimit;
94 | }
95 |
96 | public long publisherLimit()
97 | {
98 | return publisherLimit;
99 | }
100 |
101 | public int sessionId()
102 | {
103 | return sessionId;
104 | }
105 |
106 | public long publisherBufferRemaining()
107 | {
108 | return publisherLimit - publisherPosition;
109 | }
110 |
111 | public long buffered()
112 | {
113 | return Math.max(0, publisherPosition - senderPosition);
114 | }
115 |
116 | public CharSequence channel()
117 | {
118 | return channel;
119 | }
120 |
121 | public int streamId()
122 | {
123 | return streamId;
124 | }
125 |
126 | public PublisherCounterSet copy()
127 | {
128 | final PublisherCounterSet copy = new PublisherCounterSet();
129 | copy.reset(channel().toString(), sessionId(), streamId());
130 | copy.backPressureEvents(backPressureEvents());
131 | copy.publisherLimit(publisherLimit());
132 | copy.publisherPosition(publisherPosition());
133 | copy.senderLimit(senderLimit());
134 | copy.senderPosition(senderPosition());
135 | return copy;
136 | }
137 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/SessionKeyed.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | /**
21 | * Interface describing an entity that can be identified by channel, streamId, and sessionId.
22 | */
23 | public interface SessionKeyed
24 | {
25 | /**
26 | * Sets the relevant fields on this entity.
27 | *
28 | * @param channel the channel
29 | * @param sessionId the sessionId
30 | * @param streamId the streamId
31 | */
32 | void reset(CharSequence channel, int sessionId, int streamId);
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/SubscriberCounterSet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | import org.agrona.collections.Long2LongHashMap;
21 |
22 | /**
23 | * Models counters associated with a subscriber.
24 | */
25 | public final class SubscriberCounterSet implements SessionKeyed
26 | {
27 | private final StringBuilder channel = new StringBuilder();
28 | private long receiverPosition;
29 | private long receiverHighWaterMark;
30 | private int sessionId;
31 | private int streamId;
32 | private Long2LongHashMap subscriberPositions = new Long2LongHashMap(Long.MIN_VALUE);
33 |
34 | /**
35 | * {@inheritDoc}
36 | */
37 | @Override
38 | public void reset(final CharSequence channel, final int sessionId, final int streamId)
39 | {
40 | this.channel.setLength(0);
41 | this.channel.append(channel);
42 | this.streamId = streamId;
43 | this.sessionId = sessionId;
44 | subscriberPositions.clear();
45 | }
46 |
47 | public int subscriberCount()
48 | {
49 | return subscriberPositions.size();
50 | }
51 |
52 | public Long2LongHashMap subscriberPositions()
53 | {
54 | return subscriberPositions;
55 | }
56 |
57 | public void subscriberPosition(final long registrationId, final long subscriberPosition)
58 | {
59 | subscriberPositions.put(registrationId, subscriberPosition);
60 | }
61 |
62 | public void receiverPosition(final long receiverPosition)
63 | {
64 | this.receiverPosition = receiverPosition;
65 | }
66 |
67 | public void receiverHighWaterMark(final long receiverHighWaterMark)
68 | {
69 | this.receiverHighWaterMark = receiverHighWaterMark;
70 | }
71 |
72 | public void sessionId(final int sessionId)
73 | {
74 | this.sessionId = sessionId;
75 | }
76 |
77 | public long receiverPosition()
78 | {
79 | return receiverPosition;
80 | }
81 |
82 | public long receiverHighWaterMark()
83 | {
84 | return receiverHighWaterMark;
85 | }
86 |
87 | public int sessionId()
88 | {
89 | return sessionId;
90 | }
91 |
92 | public long incompleteData()
93 | {
94 | return Math.max(0, receiverHighWaterMark - receiverPosition);
95 | }
96 |
97 | public long inflight(final PublisherCounterSet publisher)
98 | {
99 | return Math.max(0, receiverHighWaterMark - publisher.senderPosition());
100 | }
101 |
102 | public CharSequence channel()
103 | {
104 | return channel;
105 | }
106 |
107 | public int streamId()
108 | {
109 | return streamId;
110 | }
111 |
112 | public SubscriberCounterSet copy()
113 | {
114 | final SubscriberCounterSet copy = new SubscriberCounterSet();
115 | copy.reset(channel(), sessionId(), streamId());
116 | copy.receiverHighWaterMark(receiverHighWaterMark());
117 | copy.receiverPosition(receiverPosition());
118 |
119 | final Long2LongHashMap.KeyIterator registrationIds = subscriberPositions.keySet().iterator();
120 | while (registrationIds.hasNext())
121 | {
122 | final long registrationId = registrationIds.nextValue();
123 | copy.subscriberPosition(registrationId, subscriberPositions.get(registrationId));
124 | }
125 | return copy;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/model/SystemCounters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.model;
19 |
20 | /**
21 | * Models counters associated with a MediaDriver.
22 | */
23 | public final class SystemCounters
24 | {
25 | private long bytesSent;
26 | private long bytesReceived;
27 | private long naksSent;
28 | private long naksReceived;
29 | private long errors;
30 | private long clientTimeouts;
31 |
32 | public long bytesSent()
33 | {
34 | return bytesSent;
35 | }
36 |
37 | public void bytesSent(final long bytesSent)
38 | {
39 | this.bytesSent = bytesSent;
40 | }
41 |
42 | public long bytesReceived()
43 | {
44 | return bytesReceived;
45 | }
46 |
47 | public void bytesReceived(final long bytesReceived)
48 | {
49 | this.bytesReceived = bytesReceived;
50 | }
51 |
52 | public long naksSent()
53 | {
54 | return naksSent;
55 | }
56 |
57 | public void naksSent(final long naksSent)
58 | {
59 | this.naksSent = naksSent;
60 | }
61 |
62 | public long naksReceived()
63 | {
64 | return naksReceived;
65 | }
66 |
67 | public void naksReceived(final long naksReceived)
68 | {
69 | this.naksReceived = naksReceived;
70 | }
71 |
72 | public long errors()
73 | {
74 | return errors;
75 | }
76 |
77 | public void errors(final long errors)
78 | {
79 | this.errors = errors;
80 | }
81 |
82 | public long clientTimeouts()
83 | {
84 | return clientTimeouts;
85 | }
86 |
87 | public void clientTimeouts(final long clientTimeouts)
88 | {
89 | this.clientTimeouts = clientTimeouts;
90 | }
91 |
92 | public void copyInto(final SystemCounters systemCounters)
93 | {
94 | systemCounters.bytesSent = bytesSent;
95 | systemCounters.bytesReceived = bytesReceived;
96 | systemCounters.naksSent = naksSent;
97 | systemCounters.naksReceived = naksReceived;
98 | systemCounters.errors = errors;
99 | systemCounters.clientTimeouts = clientTimeouts;
100 | }
101 |
102 | @Override
103 | public String toString()
104 | {
105 | return "SystemCounters{" +
106 | "bytesSent=" + bytesSent +
107 | ", bytesReceived=" + bytesReceived +
108 | ", naksSent=" + naksSent +
109 | ", naksReceived=" + naksReceived +
110 | ", errors=" + errors +
111 | ", clientTimeouts=" + clientTimeouts +
112 | '}';
113 | }
114 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/ChannelConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | final class ChannelConfig
21 | {
22 | private static final String AETHER_CHANNEL_DEFAULT = "aeron:udp?endpoint=localhost:15566";
23 | private static final String AETHER_TRANSPORT_CHANNEL_PROP_NAME = "aether.transport.channel";
24 | private static final String AETHER_TRANSPORT_STREAM_ID_PROP_NAME = "aether.transport.streamId";
25 | static final String AETHER_CHANNEL = System.getProperty(AETHER_TRANSPORT_CHANNEL_PROP_NAME,
26 | AETHER_CHANNEL_DEFAULT);
27 | static final int AETHER_STREAM_ID = Integer.getInteger(AETHER_TRANSPORT_STREAM_ID_PROP_NAME, 0xAE01);
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/CounterSnapshotPublisher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | import com.aitusoftware.aether.annotation.CallerOwned;
21 | import com.aitusoftware.aether.event.CounterSnapshotListener;
22 | import com.aitusoftware.aether.model.SystemCounters;
23 | import com.aitusoftware.aether.model.PublisherCounterSet;
24 | import com.aitusoftware.aether.model.SubscriberCounterSet;
25 | import io.aeron.Aeron;
26 | import io.aeron.CommonContext;
27 | import io.aeron.Publication;
28 | import org.agrona.CloseHelper;
29 | import org.agrona.ExpandableArrayBuffer;
30 | import org.agrona.MutableDirectBuffer;
31 |
32 | import java.util.List;
33 |
34 | /**
35 | * An implementation of {@code CounterSnapshotListener} that publishes to an Aeron {@code Publication}.
36 | */
37 | public final class CounterSnapshotPublisher implements CounterSnapshotListener, AutoCloseable
38 | {
39 | private final MutableDirectBuffer buffer = new ExpandableArrayBuffer();
40 | private final SnapshotSerialiser serialiser = new SnapshotSerialiser();
41 | private final Publication publication;
42 | private final Aeron aeronClient;
43 | private final boolean ownsAeronClient;
44 |
45 | /**
46 | * Construct a new publisher using runtime configuration.
47 | */
48 | public CounterSnapshotPublisher()
49 | {
50 | this(new Context());
51 | }
52 |
53 | /**
54 | * Construct a new publisher from the supplied context.
55 | *
56 | * @param context configuration context
57 | */
58 | public CounterSnapshotPublisher(final Context context)
59 | {
60 | if (context.aeronClient() == null)
61 | {
62 | aeronClient = Aeron.connect(new Aeron.Context()
63 | .aeronDirectoryName(context.aeronDirectoryName()));
64 | ownsAeronClient = true;
65 | }
66 | else
67 | {
68 | aeronClient = context.aeronClient();
69 | ownsAeronClient = false;
70 | }
71 |
72 | publication = aeronClient.addPublication(context.aetherChannel(), context.aetherStreamId());
73 | }
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | @Override
79 | public void onSnapshot(
80 | final String label,
81 | final long timestamp,
82 | @CallerOwned final List publisherCounters,
83 | @CallerOwned final List subscriberCounters,
84 | @CallerOwned final SystemCounters systemCounters)
85 | {
86 | final int length = serialiser.serialiseSnapshot(
87 | label, timestamp, publisherCounters, subscriberCounters, systemCounters, buffer);
88 |
89 | int retryCount = 5;
90 | long result;
91 | do
92 | {
93 | result = publication.offer(buffer, 0, length);
94 | if (result == Publication.CLOSED)
95 | {
96 | throw new IllegalStateException("Publication closed");
97 | }
98 | }
99 | while (--retryCount != 0 && result < 0);
100 | }
101 |
102 | /**
103 | * {@inheritDoc}
104 | */
105 | @Override
106 | public void close()
107 | {
108 | CloseHelper.close(publication);
109 | if (ownsAeronClient)
110 | {
111 | CloseHelper.close(aeronClient);
112 | }
113 | }
114 |
115 | /**
116 | * Configuration context.
117 | */
118 | public static final class Context
119 | {
120 | private Aeron aeronClient;
121 | private String aetherChannel = ChannelConfig.AETHER_CHANNEL;
122 | private int aetherStreamId = ChannelConfig.AETHER_STREAM_ID;
123 | private String aeronDirectoryName = CommonContext.getAeronDirectoryName();
124 |
125 | public Context aeronClient(final Aeron aeronClient)
126 | {
127 | this.aeronClient = aeronClient;
128 | return this;
129 | }
130 |
131 | public Aeron aeronClient()
132 | {
133 | return aeronClient;
134 | }
135 |
136 | public Context aetherChannel(final String channel)
137 | {
138 | this.aetherChannel = channel;
139 | return this;
140 | }
141 |
142 | public String aetherChannel()
143 | {
144 | return aetherChannel;
145 | }
146 |
147 | public Context aetherStreamId(final int streamId)
148 | {
149 | this.aetherStreamId = streamId;
150 | return this;
151 | }
152 |
153 | public int aetherStreamId()
154 | {
155 | return aetherStreamId;
156 | }
157 |
158 | public Context aeronDirectoryName(final String aeronDirectoryName)
159 | {
160 | this.aeronDirectoryName = aeronDirectoryName;
161 | return this;
162 | }
163 |
164 | public String aeronDirectoryName()
165 | {
166 | return aeronDirectoryName;
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/CounterSnapshotSubscriber.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | import com.aitusoftware.aether.event.CounterSnapshotListener;
21 | import io.aeron.Aeron;
22 | import io.aeron.CommonContext;
23 | import io.aeron.FragmentAssembler;
24 | import io.aeron.Subscription;
25 | import io.aeron.logbuffer.FragmentHandler;
26 | import io.aeron.logbuffer.Header;
27 | import org.agrona.CloseHelper;
28 | import org.agrona.DirectBuffer;
29 |
30 | /**
31 | * A listener to data published by a {@code CounterSnapshotPublisher}.
32 | */
33 | public final class CounterSnapshotSubscriber implements FragmentHandler, AutoCloseable
34 | {
35 | private final SnapshotDeserialiser deserialiser = new SnapshotDeserialiser();
36 | private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this);
37 | private final Subscription subscription;
38 | private final CounterSnapshotListener counterSnapshotListener;
39 | private final Aeron aeronClient;
40 | private final boolean ownsAeronClient;
41 |
42 | /**
43 | * Creates a new subscriber using runtime configuration.
44 | */
45 | public CounterSnapshotSubscriber()
46 | {
47 | this(new Context());
48 | }
49 |
50 | /**
51 | * Creates a new subscriber from the supplied context.
52 | * @param context configuration context
53 | */
54 | public CounterSnapshotSubscriber(final Context context)
55 | {
56 | if (context.aeronClient() == null)
57 | {
58 | aeronClient = Aeron.connect(new Aeron.Context()
59 | .aeronDirectoryName(context.aeronDirectoryName()));
60 | ownsAeronClient = true;
61 | }
62 | else
63 | {
64 | aeronClient = context.aeronClient();
65 | ownsAeronClient = false;
66 | }
67 |
68 | subscription = aeronClient.addSubscription(context.aetherChannel(), context.aetherStreamId());
69 | counterSnapshotListener = context.counterSnapshotListener();
70 | }
71 |
72 | /**
73 | * {@inheritDoc}
74 | */
75 | @Override
76 | public void close() throws Exception
77 | {
78 | CloseHelper.close(subscription);
79 | if (ownsAeronClient)
80 | {
81 | CloseHelper.close(aeronClient);
82 | }
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | @Override
89 | public void onFragment(
90 | final DirectBuffer buffer,
91 | final int offset,
92 | final int length,
93 | final Header header)
94 | {
95 | deserialiser.deserialiseSnapshot(buffer, offset, counterSnapshotListener);
96 | }
97 |
98 | /**
99 | * Poll the Aeron {@code Subscription}.
100 | *
101 | * @return number of fragments processed
102 | */
103 | public int doWork()
104 | {
105 | return subscription.poll(fragmentAssembler, 100);
106 | }
107 |
108 | /**
109 | * Configuration context.
110 | */
111 | public static final class Context
112 | {
113 | private Aeron aeronClient;
114 | private String aetherChannel = ChannelConfig.AETHER_CHANNEL;
115 | private int aetherStreamId = ChannelConfig.AETHER_STREAM_ID;
116 | private String aeronDirectoryName = CommonContext.getAeronDirectoryName();
117 | private CounterSnapshotListener counterSnapshotListener;
118 |
119 | public Context counterSnapshotListener(final CounterSnapshotListener counterSnapshotListener)
120 | {
121 | this.counterSnapshotListener = counterSnapshotListener;
122 | return this;
123 | }
124 |
125 | public CounterSnapshotListener counterSnapshotListener()
126 | {
127 | return counterSnapshotListener;
128 | }
129 |
130 | public Context aeronClient(final Aeron aeronClient)
131 | {
132 | this.aeronClient = aeronClient;
133 | return this;
134 | }
135 |
136 | public Aeron aeronClient()
137 | {
138 | return aeronClient;
139 | }
140 |
141 | public Context aetherChannel(final String channel)
142 | {
143 | this.aetherChannel = channel;
144 | return this;
145 | }
146 |
147 | public String aetherChannel()
148 | {
149 | return aetherChannel;
150 | }
151 |
152 | public Context aetherStreamId(final int streamId)
153 | {
154 | this.aetherStreamId = streamId;
155 | return this;
156 | }
157 |
158 | public int aetherStreamId()
159 | {
160 | return aetherStreamId;
161 | }
162 |
163 | public Context aeronDirectoryName(final String aeronDirectoryName)
164 | {
165 | this.aeronDirectoryName = aeronDirectoryName;
166 | return this;
167 | }
168 |
169 | public String aeronDirectoryName()
170 | {
171 | return aeronDirectoryName;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/SnapshotDeserialiser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import com.aitusoftware.aether.event.CounterSnapshotListener;
24 | import com.aitusoftware.aether.model.PublisherCounterSet;
25 | import com.aitusoftware.aether.model.SubscriberCounterSet;
26 | import com.aitusoftware.aether.model.SystemCounters;
27 |
28 | import org.agrona.DirectBuffer;
29 |
30 | final class SnapshotDeserialiser
31 | {
32 | private final StringBuilder charBuffer = new StringBuilder();
33 | private final SystemCounters systemCounters = new SystemCounters();
34 |
35 | void deserialiseSnapshot(
36 | final DirectBuffer buffer,
37 | final int offset,
38 | final CounterSnapshotListener listener)
39 | {
40 | int relativeOffset = offset;
41 | final int headerId = buffer.getInt(relativeOffset);
42 | relativeOffset += Integer.BYTES;
43 | final byte version = buffer.getByte(relativeOffset);
44 | relativeOffset += Byte.BYTES;
45 | if (Versions.SNAPSHOT_HEADER_ID != headerId)
46 | {
47 | throw new IllegalArgumentException("Unknown message type: " + headerId);
48 | }
49 | if (Versions.VERSION != version)
50 | {
51 | throw new IllegalArgumentException("Unknown version: " + version);
52 | }
53 | final int labelLength = buffer.getInt(relativeOffset);
54 | relativeOffset += Integer.BYTES;
55 | final StringBuilder label = new StringBuilder();
56 | for (int i = 0; i < labelLength; i++)
57 | {
58 | label.append(buffer.getChar(relativeOffset));
59 | relativeOffset += Character.BYTES;
60 | }
61 | final long timestamp = buffer.getLong(relativeOffset);
62 | relativeOffset += Long.BYTES;
63 |
64 | systemCounters.bytesSent(buffer.getLong(relativeOffset));
65 | relativeOffset += Long.BYTES;
66 | systemCounters.bytesReceived(buffer.getLong(relativeOffset));
67 | relativeOffset += Long.BYTES;
68 | systemCounters.naksSent(buffer.getLong(relativeOffset));
69 | relativeOffset += Long.BYTES;
70 | systemCounters.naksReceived(buffer.getLong(relativeOffset));
71 | relativeOffset += Long.BYTES;
72 | systemCounters.errors(buffer.getLong(relativeOffset));
73 | relativeOffset += Long.BYTES;
74 | systemCounters.clientTimeouts(buffer.getLong(relativeOffset));
75 | relativeOffset += Long.BYTES;
76 |
77 | final List publisherCounters = new ArrayList<>();
78 | relativeOffset = readPublisherCounters(relativeOffset, publisherCounters, buffer);
79 | final List subscriberCounters = new ArrayList<>();
80 | relativeOffset = readSubscriberCounters(relativeOffset, subscriberCounters, buffer);
81 |
82 | listener.onSnapshot(label.toString(), timestamp, publisherCounters, subscriberCounters, systemCounters);
83 | }
84 |
85 | private int readSubscriberCounters(
86 | final int offset,
87 | final List subscriberCounters,
88 | final DirectBuffer buffer)
89 | {
90 | int localOffset = offset;
91 | final int count = buffer.getInt(localOffset);
92 | localOffset += Integer.BYTES;
93 |
94 | for (int i = 0; i < count; i++)
95 | {
96 | final int labelLength = buffer.getInt(localOffset);
97 |
98 | localOffset += Integer.BYTES;
99 | charBuffer.setLength(0);
100 | for (int c = 0; c < labelLength; c++)
101 | {
102 | charBuffer.append(buffer.getChar(localOffset));
103 | localOffset += Character.BYTES;
104 | }
105 | final int streamId = buffer.getInt(localOffset);
106 | localOffset += Integer.BYTES;
107 | final int sessionId = buffer.getInt(localOffset);
108 | localOffset += Integer.BYTES;
109 |
110 | final int subscriberCount = buffer.getInt(localOffset);
111 | localOffset += Integer.BYTES;
112 | final SubscriberCounterSet counterSet = new SubscriberCounterSet();
113 | counterSet.reset(charBuffer.toString(), sessionId, streamId);
114 |
115 | for (int j = 0; j < subscriberCount; j++)
116 | {
117 | final long regId = buffer.getLong(localOffset);
118 | localOffset += Long.BYTES;
119 | final long value = buffer.getLong(localOffset);
120 | localOffset += Long.BYTES;
121 | counterSet.subscriberPosition(regId, value);
122 | }
123 |
124 | final long receiverPosition = buffer.getLong(localOffset);
125 | localOffset += Long.BYTES;
126 | final long receiverHighWaterMark = buffer.getLong(localOffset);
127 | localOffset += Long.BYTES;
128 |
129 | counterSet.receiverPosition(receiverPosition);
130 | counterSet.receiverHighWaterMark(receiverHighWaterMark);
131 | subscriberCounters.add(counterSet);
132 | }
133 | return localOffset;
134 | }
135 |
136 | private int readPublisherCounters(
137 | final int offset,
138 | final List publisherCounters,
139 | final DirectBuffer buffer)
140 | {
141 | int localOffset = offset;
142 | final int count = buffer.getInt(localOffset);
143 | localOffset += Integer.BYTES;
144 | for (int i = 0; i < count; i++)
145 | {
146 | final int labelLength = buffer.getInt(localOffset);
147 | localOffset += Integer.BYTES;
148 | charBuffer.setLength(0);
149 | for (int c = 0; c < labelLength; c++)
150 | {
151 | charBuffer.append(buffer.getChar(localOffset));
152 | localOffset += Character.BYTES;
153 | }
154 | final int streamId = buffer.getInt(localOffset);
155 | localOffset += Integer.BYTES;
156 | final int sessionId = buffer.getInt(localOffset);
157 | localOffset += Integer.BYTES;
158 |
159 | final long publisherPosition = buffer.getLong(localOffset);
160 | localOffset += Long.BYTES;
161 | final long backPressureEvents = buffer.getLong(localOffset);
162 | localOffset += Long.BYTES;
163 | final long senderPosition = buffer.getLong(localOffset);
164 | localOffset += Long.BYTES;
165 | final long senderLimit = buffer.getLong(localOffset);
166 | localOffset += Long.BYTES;
167 | final long publisherLimit = buffer.getLong(localOffset);
168 | localOffset += Long.BYTES;
169 |
170 | final PublisherCounterSet counterSet = new PublisherCounterSet();
171 | counterSet.reset(charBuffer.toString(), sessionId, streamId);
172 | counterSet.publisherPosition(publisherPosition);
173 | counterSet.publisherLimit(publisherLimit);
174 | counterSet.backPressureEvents(backPressureEvents);
175 | counterSet.senderPosition(senderPosition);
176 | counterSet.senderLimit(senderLimit);
177 |
178 | publisherCounters.add(counterSet);
179 | }
180 | return localOffset;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/SnapshotSerialiser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | import com.aitusoftware.aether.annotation.CallerOwned;
21 | import com.aitusoftware.aether.model.PublisherCounterSet;
22 | import com.aitusoftware.aether.model.SubscriberCounterSet;
23 | import com.aitusoftware.aether.model.SystemCounters;
24 |
25 | import org.agrona.MutableDirectBuffer;
26 |
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | final class SnapshotSerialiser
31 | {
32 | int serialiseSnapshot(
33 | final String label,
34 | final long timestamp,
35 | @CallerOwned final List publisherCounters,
36 | @CallerOwned final List subscriberCounters,
37 | @CallerOwned final SystemCounters systemCounters,
38 | final MutableDirectBuffer buffer)
39 | {
40 | int offset = 0;
41 | buffer.putInt(offset, Versions.SNAPSHOT_HEADER_ID);
42 | offset += Integer.BYTES;
43 | buffer.putByte(offset, Versions.VERSION);
44 | offset += Byte.BYTES;
45 | buffer.putInt(offset, label.length());
46 | offset += Integer.BYTES;
47 | for (int i = 0; i < label.length(); i++)
48 | {
49 | buffer.putChar(offset, label.charAt(i));
50 | offset += Character.BYTES;
51 | }
52 | buffer.putLong(offset, timestamp);
53 | offset += Long.BYTES;
54 | buffer.putLong(offset, systemCounters.bytesSent());
55 | offset += Long.BYTES;
56 | buffer.putLong(offset, systemCounters.bytesReceived());
57 | offset += Long.BYTES;
58 | buffer.putLong(offset, systemCounters.naksSent());
59 | offset += Long.BYTES;
60 | buffer.putLong(offset, systemCounters.naksReceived());
61 | offset += Long.BYTES;
62 | buffer.putLong(offset, systemCounters.errors());
63 | offset += Long.BYTES;
64 | buffer.putLong(offset, systemCounters.clientTimeouts());
65 | offset += Long.BYTES;
66 | offset = writePublisherCounters(offset, publisherCounters, buffer);
67 | offset = writeSubscriberCounters(offset, subscriberCounters, buffer);
68 | return offset;
69 | }
70 |
71 | private static int writeSubscriberCounters(
72 | final int offset,
73 | final List subscriberCounters,
74 | final MutableDirectBuffer buffer)
75 | {
76 | int localOffset = offset;
77 | buffer.putInt(localOffset, subscriberCounters.size());
78 | localOffset += Integer.BYTES;
79 | for (final SubscriberCounterSet subscriberCounter : subscriberCounters)
80 | {
81 | buffer.putInt(localOffset, subscriberCounter.channel().length());
82 | localOffset += Integer.BYTES;
83 | for (int i = 0; i < subscriberCounter.channel().length(); i++)
84 | {
85 | buffer.putChar(localOffset, subscriberCounter.channel().charAt(i));
86 | localOffset += Character.BYTES;
87 | }
88 | buffer.putInt(localOffset, subscriberCounter.streamId());
89 | localOffset += Integer.BYTES;
90 | buffer.putInt(localOffset, subscriberCounter.sessionId());
91 | localOffset += Integer.BYTES;
92 |
93 | buffer.putInt(localOffset, subscriberCounter.subscriberCount());
94 | localOffset += Integer.BYTES;
95 | for (final Map.Entry positions : subscriberCounter.subscriberPositions().entrySet())
96 | {
97 | buffer.putLong(localOffset, positions.getKey());
98 | localOffset += Long.BYTES;
99 | buffer.putLong(localOffset, positions.getValue());
100 | localOffset += Long.BYTES;
101 | }
102 |
103 | buffer.putLong(localOffset, subscriberCounter.receiverPosition());
104 | localOffset += Long.BYTES;
105 | buffer.putLong(localOffset, subscriberCounter.receiverHighWaterMark());
106 | localOffset += Long.BYTES;
107 | }
108 | return localOffset;
109 | }
110 |
111 | private static int writePublisherCounters(
112 | final int offset,
113 | final List publisherCounters,
114 | final MutableDirectBuffer buffer)
115 | {
116 | int localOffset = offset;
117 | buffer.putInt(localOffset, publisherCounters.size());
118 | localOffset += Integer.BYTES;
119 | for (final PublisherCounterSet publisherCounter : publisherCounters)
120 | {
121 | buffer.putInt(localOffset, publisherCounter.channel().length());
122 | localOffset += Integer.BYTES;
123 | for (int i = 0; i < publisherCounter.channel().length(); i++)
124 | {
125 | buffer.putChar(localOffset, publisherCounter.channel().charAt(i));
126 | localOffset += Character.BYTES;
127 | }
128 | buffer.putInt(localOffset, publisherCounter.streamId());
129 | localOffset += Integer.BYTES;
130 | buffer.putInt(localOffset, publisherCounter.sessionId());
131 | localOffset += Integer.BYTES;
132 |
133 | buffer.putLong(localOffset, publisherCounter.publisherPosition());
134 | localOffset += Long.BYTES;
135 | buffer.putLong(localOffset, publisherCounter.backPressureEvents());
136 | localOffset += Long.BYTES;
137 | buffer.putLong(localOffset, publisherCounter.senderPosition());
138 | localOffset += Long.BYTES;
139 | buffer.putLong(localOffset, publisherCounter.senderLimit());
140 | localOffset += Long.BYTES;
141 | buffer.putLong(localOffset, publisherCounter.publisherLimit());
142 | localOffset += Long.BYTES;
143 | }
144 | return localOffset;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/com/aitusoftware/aether/transport/Versions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | final class Versions
21 | {
22 | static final int SNAPSHOT_HEADER_ID = 0xAE00_5555;
23 | static final byte VERSION = 1;
24 |
25 | private Versions()
26 | {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/CountersPollerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | import java.nio.charset.StandardCharsets;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | import com.aitusoftware.aether.event.CounterValueListener;
27 |
28 | import org.agrona.CloseHelper;
29 | import org.agrona.DirectBuffer;
30 | import org.agrona.concurrent.SleepingMillisIdleStrategy;
31 | import org.agrona.concurrent.SystemEpochClock;
32 | import org.agrona.concurrent.UnsafeBuffer;
33 | import org.junit.jupiter.api.AfterEach;
34 | import org.junit.jupiter.api.BeforeEach;
35 | import org.junit.jupiter.api.Test;
36 |
37 | import io.aeron.Aeron;
38 | import io.aeron.ConcurrentPublication;
39 | import io.aeron.FragmentAssembler;
40 | import io.aeron.Subscription;
41 | import io.aeron.driver.MediaDriver;
42 | import io.aeron.driver.ThreadingMode;
43 | import io.aeron.driver.status.PublisherLimit;
44 | import io.aeron.driver.status.PublisherPos;
45 | import io.aeron.driver.status.ReceiverHwm;
46 | import io.aeron.driver.status.ReceiverPos;
47 | import io.aeron.driver.status.SenderBpe;
48 | import io.aeron.driver.status.SenderLimit;
49 | import io.aeron.driver.status.SenderPos;
50 | import io.aeron.driver.status.SubscriberPos;
51 | import io.aeron.logbuffer.FragmentHandler;
52 | import io.aeron.logbuffer.Header;
53 |
54 | class CountersPollerTest
55 | {
56 | private static final String CHANNEL = "aeron:udp?endpoint=localhost:14567";
57 | private static final int STREAM_ID = 7;
58 |
59 | private final CapturingCounterValueListener valueListener = new CapturingCounterValueListener();
60 | private CountersPoller countersPoller;
61 | private MediaDriver mediaDriver;
62 | private Aeron aeron;
63 | private ConcurrentPublication publication;
64 | private Subscription subscription;
65 |
66 | @BeforeEach
67 | void setUp()
68 | {
69 | mediaDriver = MediaDriver.launchEmbedded(new MediaDriver.Context()
70 | .threadingMode(ThreadingMode.SHARED)
71 | .sharedIdleStrategy(new SleepingMillisIdleStrategy(1)));
72 | countersPoller = new CountersPoller(
73 | valueListener, "label", mediaDriver.aeronDirectoryName(), new SystemEpochClock());
74 | aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));
75 | publication = aeron.addPublication(CHANNEL, STREAM_ID);
76 | subscription = aeron.addSubscription(CHANNEL, STREAM_ID);
77 | }
78 |
79 | @AfterEach
80 | void tearDown()
81 | {
82 | CloseHelper.close(aeron);
83 | CloseHelper.close(mediaDriver);
84 | }
85 |
86 | @Test
87 | void shouldReportPublisherAndSubscriberCounters()
88 | {
89 | final byte[] payload = "payload".getBytes(StandardCharsets.UTF_8);
90 | final UnsafeBuffer message = new UnsafeBuffer(payload);
91 | while (publication.offer(message, 0, payload.length) < 0)
92 | {
93 | // spin
94 | }
95 |
96 | final RecordingFragmentHandler fragmentHandler = new RecordingFragmentHandler();
97 | while (!fragmentHandler.messageReceived)
98 | {
99 | subscription.poll(new FragmentAssembler(fragmentHandler), 100);
100 | }
101 | countersPoller.doWork();
102 |
103 | assertThat(valueListener.values.size()).isEqualTo(14);
104 | assertCaptureContains(PublisherPos.PUBLISHER_POS_TYPE_ID, valueListener.values);
105 | assertCaptureContains(PublisherLimit.PUBLISHER_LIMIT_TYPE_ID, valueListener.values);
106 | assertCaptureContains(SenderPos.SENDER_POSITION_TYPE_ID, valueListener.values);
107 | assertCaptureContains(SenderLimit.SENDER_LIMIT_TYPE_ID, valueListener.values);
108 | assertCaptureContains(SenderBpe.SENDER_BPE_TYPE_ID, valueListener.values);
109 | assertCaptureContains(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, valueListener.values);
110 | assertCaptureContains(ReceiverPos.RECEIVER_POS_TYPE_ID, valueListener.values);
111 | assertCaptureContains(ReceiverHwm.RECEIVER_HWM_TYPE_ID, valueListener.values);
112 | }
113 |
114 | private static void assertCaptureContains(final int typeId, final List values)
115 | {
116 | assertThat(values.stream().anyMatch(c ->
117 | {
118 | return c.typeId == typeId &&
119 | c.uri.toString().equals(CHANNEL) &&
120 | c.streamId == STREAM_ID;
121 | })).isTrue();
122 | }
123 |
124 | private static final class CapturingCounterValueListener implements CounterValueListener
125 | {
126 | private final List values = new ArrayList<>();
127 |
128 | @Override
129 | public void onCounterEvent(
130 | final int counterId, final int counterTypeId, final CharSequence channel,
131 | final int sessionId, final int streamId,
132 | final long registrationId, final long value)
133 | {
134 | values.add(new CapturedCounterValue(counterTypeId, channel.toString(), streamId, value));
135 | }
136 |
137 | @Override
138 | public void onEndOfBatch(final String label)
139 | {
140 | }
141 | }
142 |
143 | private static final class CapturedCounterValue
144 | {
145 | private final int typeId;
146 | private final CharSequence uri;
147 | private final int streamId;
148 | private final long value;
149 |
150 | CapturedCounterValue(final int typeId, final CharSequence uri, final int streamId, final long value)
151 | {
152 | this.typeId = typeId;
153 | this.uri = uri;
154 | this.streamId = streamId;
155 | this.value = value;
156 | }
157 | }
158 |
159 | private static final class RecordingFragmentHandler implements FragmentHandler
160 | {
161 | boolean messageReceived = false;
162 |
163 | @Override
164 | public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)
165 | {
166 | messageReceived = true;
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/aggregation/StreamRateTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.aggregation;
19 |
20 | import org.junit.jupiter.api.Test;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.List;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | import static com.google.common.truth.Truth.assertThat;
28 |
29 | class StreamRateTest
30 | {
31 | private static final long BASE_TIME = System.currentTimeMillis();
32 | private final StreamRate streamRate = new StreamRate(
33 | Arrays.asList(
34 | new RateBucket(5, TimeUnit.SECONDS),
35 | new RateBucket(1, TimeUnit.MINUTES)));
36 |
37 | @Test
38 | void shouldCalculateSameAverageRateInMultipleBuckets()
39 | {
40 | for (int i = 0; i < 700; i++)
41 | {
42 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(i), i * 5000);
43 | }
44 |
45 | final List rates = new ArrayList<>();
46 | streamRate.consumeRates(((duration, durationUnit, bytesPerUnit) ->
47 | {
48 | rates.add(new Rate(duration, durationUnit, bytesPerUnit));
49 | }));
50 |
51 | assertThat(rates.size()).isEqualTo(2);
52 | assertThat(rates.get(0).bytesPerSecond).isEqualTo(5000L);
53 | assertThat(rates.get(1).bytesPerSecond).isEqualTo(5000L);
54 | }
55 |
56 | @Test
57 | void shouldMaintainAverageRate()
58 | {
59 | streamRate.streamPosition(BASE_TIME, 0);
60 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(1), 2000);
61 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(2), 4999);
62 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(3), 5000);
63 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(4), 6000);
64 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(5), 7000);
65 | final List rates = new ArrayList<>();
66 | streamRate.consumeRates(((duration, durationUnit, bytesPerUnit) ->
67 | {
68 | rates.add(new Rate(duration, durationUnit, bytesPerUnit));
69 | }));
70 |
71 | assertThat(rates.size()).isEqualTo(2);
72 | assertThat(rates.get(0).duration).isEqualTo(5L);
73 | assertThat(rates.get(0).durationUnit).isEqualTo(TimeUnit.SECONDS);
74 | assertThat(rates.get(0).bytesPerSecond).isEqualTo(1250L);
75 | }
76 |
77 | @Test
78 | void shouldCalculateAverageRatesOfMultipleTimeWindows()
79 | {
80 | streamRate.streamPosition(BASE_TIME, 0);
81 | for (int i = 1; i <= 600 / 5; i++)
82 | {
83 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(i),
84 | (i * i) + 2000 + i * 20);
85 | }
86 | final List rates = new ArrayList<>();
87 | streamRate.consumeRates(((duration, durationUnit, bytesPerUnit) ->
88 | {
89 | rates.add(new Rate(duration, durationUnit, bytesPerUnit));
90 | }));
91 |
92 | assertThat(rates.size()).isEqualTo(2);
93 | assertThat(rates.get(1).bytesPerSecond).isLessThan(rates.get(0).bytesPerSecond);
94 | assertThat(rates.get(0).duration).isEqualTo(5L);
95 | assertThat(rates.get(0).durationUnit).isEqualTo(TimeUnit.SECONDS);
96 | assertThat(rates.get(0).bytesPerSecond).isEqualTo(256L);
97 | assertThat(rates.get(1).duration).isEqualTo(1L);
98 | assertThat(rates.get(1).durationUnit).isEqualTo(TimeUnit.MINUTES);
99 | assertThat(rates.get(1).bytesPerSecond).isEqualTo(201L);
100 | }
101 |
102 | @Test
103 | void shouldFilterUnnecessaryUpdates()
104 | {
105 | streamRate.streamPosition(BASE_TIME, 0);
106 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(1), 2000);
107 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(1) + 5, 2000);
108 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(1) + 15, 2200);
109 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(1) + 30, 2650);
110 | streamRate.streamPosition(BASE_TIME + TimeUnit.SECONDS.toMillis(2), 4000);
111 | final List rates = new ArrayList<>();
112 | streamRate.consumeRates(((duration, durationUnit, bytesPerUnit) ->
113 | {
114 | rates.add(new Rate(duration, durationUnit, bytesPerUnit));
115 | }));
116 |
117 | assertThat(rates.size()).isEqualTo(2);
118 | assertThat(rates.get(0).duration).isEqualTo(5L);
119 | assertThat(rates.get(0).durationUnit).isEqualTo(TimeUnit.SECONDS);
120 | assertThat(rates.get(0).bytesPerSecond).isEqualTo(2000L);
121 | }
122 |
123 | private static final class Rate
124 | {
125 | private final long duration;
126 | private final TimeUnit durationUnit;
127 | private final long bytesPerSecond;
128 |
129 | Rate(final long duration, final TimeUnit durationUnit, final long bytesPerSecond)
130 | {
131 | this.duration = duration;
132 | this.durationUnit = durationUnit;
133 | this.bytesPerSecond = bytesPerSecond;
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/event/CounterEventHandlerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | import java.util.Arrays;
23 | import java.util.HashMap;
24 | import java.util.List;
25 | import java.util.Map;
26 | import java.util.Set;
27 |
28 | import com.aitusoftware.aether.model.PublisherCounterSet;
29 | import com.aitusoftware.aether.model.ChannelSessionKey;
30 | import com.aitusoftware.aether.model.SubscriberCounterSet;
31 | import com.google.common.collect.Sets;
32 |
33 | import org.agrona.concurrent.SystemEpochClock;
34 | import org.junit.jupiter.api.Test;
35 |
36 | import io.aeron.driver.status.PublisherPos;
37 | import io.aeron.driver.status.ReceiverHwm;
38 | import io.aeron.driver.status.ReceiverPos;
39 | import io.aeron.driver.status.SubscriberPos;
40 |
41 | class CounterEventHandlerTest
42 | {
43 | private static final String CHANNEL = "CHANNEL";
44 | private static final int SESSION_ID = -1200003;
45 | private static final int STREAM_ID = 37;
46 | private static final String CHANNEL_2 = "CHANNEL_2";
47 | private static final String LABEL = "label";
48 | private static final int VALUE = 101_888;
49 | private static final List TEST_DATA = Arrays.asList(
50 | new CounterValue(PublisherPos.PUBLISHER_POS_TYPE_ID, CHANNEL, 11,
51 | SESSION_ID, STREAM_ID, VALUE),
52 | new CounterValue(PublisherPos.PUBLISHER_POS_TYPE_ID, CHANNEL_2, 12,
53 | SESSION_ID, STREAM_ID, VALUE),
54 | new CounterValue(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, CHANNEL, 1,
55 | SESSION_ID, STREAM_ID, VALUE),
56 | new CounterValue(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, CHANNEL, 2,
57 | SESSION_ID, STREAM_ID, VALUE),
58 | new CounterValue(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, CHANNEL, 3,
59 | SESSION_ID, STREAM_ID, VALUE),
60 | new CounterValue(ReceiverHwm.RECEIVER_HWM_TYPE_ID, CHANNEL, 8,
61 | SESSION_ID, STREAM_ID, VALUE),
62 | new CounterValue(ReceiverPos.RECEIVER_POS_TYPE_ID, CHANNEL, 8,
63 | SESSION_ID, STREAM_ID, VALUE),
64 | new CounterValue(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, CHANNEL_2, 4,
65 | SESSION_ID, STREAM_ID, VALUE),
66 | new CounterValue(SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID, CHANNEL_2, 5,
67 | SESSION_ID, STREAM_ID, VALUE),
68 | new CounterValue(ReceiverHwm.RECEIVER_HWM_TYPE_ID, CHANNEL_2, 9,
69 | SESSION_ID, STREAM_ID, VALUE),
70 | new CounterValue(ReceiverPos.RECEIVER_POS_TYPE_ID, CHANNEL_2, 10,
71 | SESSION_ID, STREAM_ID, VALUE));
72 |
73 | private final SystemSnapshot systemSnapshot = new SystemSnapshot();
74 | private final CounterEventHandler counterEventHandler = new CounterEventHandler(
75 | new CounterRepository<>(PublisherCounterSet::new),
76 | new CounterRepository<>(SubscriberCounterSet::new),
77 | systemSnapshot, new SystemEpochClock());
78 |
79 | @Test
80 | void shouldBuildModel()
81 | {
82 | for (final CounterValue testDatum : TEST_DATA)
83 | {
84 | counterEventHandler.onCounterEvent(
85 | 0, testDatum.typeId, testDatum.channel,
86 | testDatum.sessionId, testDatum.streamId,
87 | testDatum.registrationId, testDatum.value);
88 | }
89 | counterEventHandler.onEndOfBatch(LABEL);
90 |
91 | final Map>> connectionsByStream =
92 | systemSnapshot.getConnectionsByStream();
93 | final Map>> expected =
94 | new HashMap<>();
95 | final Map> channelOneMap = new HashMap<>();
96 | expected.put(new StreamKey(CHANNEL, STREAM_ID), channelOneMap);
97 | channelOneMap.put(new ChannelSessionKey(LABEL, CHANNEL, STREAM_ID, SESSION_ID),
98 | Sets.newHashSet(new ChannelSessionKey(LABEL, CHANNEL, STREAM_ID, SESSION_ID)));
99 | final Map> channelTwoMap = new HashMap<>();
100 | expected.put(new StreamKey(CHANNEL_2, STREAM_ID), channelTwoMap);
101 | channelTwoMap.put(new ChannelSessionKey(LABEL, CHANNEL_2, STREAM_ID, SESSION_ID),
102 | Sets.newHashSet(new ChannelSessionKey(LABEL, CHANNEL_2, STREAM_ID, SESSION_ID)));
103 | assertThat(connectionsByStream).isEqualTo(expected);
104 |
105 | assertThat(systemSnapshot.getPublisherCounterSet(
106 | new ChannelSessionKey(LABEL, CHANNEL, STREAM_ID, SESSION_ID)).publisherPosition())
107 | .isEqualTo(VALUE);
108 | assertThat(systemSnapshot.getPublisherCounterSet(
109 | new ChannelSessionKey(LABEL, CHANNEL_2, STREAM_ID, SESSION_ID)).publisherPosition())
110 | .isEqualTo(VALUE);
111 |
112 | final SubscriberCounterSet subscriberCounterSet =
113 | systemSnapshot.getSubscriberCounterSet(new ChannelSessionKey(LABEL, CHANNEL, STREAM_ID, SESSION_ID));
114 | assertThat(subscriberCounterSet.receiverPosition()).isEqualTo(VALUE);
115 | assertThat(subscriberCounterSet.receiverHighWaterMark()).isEqualTo(VALUE);
116 | assertThat(subscriberCounterSet.subscriberPositions().size()).isEqualTo(3);
117 | assertThat(subscriberCounterSet.subscriberPositions().keySet()).contains(1L);
118 | assertThat(subscriberCounterSet.subscriberPositions().keySet()).contains(2L);
119 | assertThat(subscriberCounterSet.subscriberPositions().keySet()).contains(3L);
120 | }
121 |
122 | private static final class CounterValue
123 | {
124 | private final int typeId;
125 | private final String channel;
126 | private final int registrationId;
127 | private final int sessionId;
128 | private final int streamId;
129 | private final long value;
130 |
131 | CounterValue(
132 | final int typeId,
133 | final String channel,
134 | final int registrationId,
135 | final int sessionId,
136 | final int streamId,
137 | final long value)
138 | {
139 | this.typeId = typeId;
140 | this.channel = channel;
141 | this.registrationId = registrationId;
142 | this.sessionId = sessionId;
143 | this.streamId = streamId;
144 | this.value = value;
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/event/CounterRepositoryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | import com.aitusoftware.aether.model.PublisherCounterSet;
23 |
24 | import org.junit.jupiter.api.Test;
25 |
26 | class CounterRepositoryTest
27 | {
28 | private static final String AERON_UDP_LOCALHOST_12355 = "aeron:udp?localhost:12355";
29 | private static final int SESSION_ID = 884477;
30 | private static final int STREAM_ID = 7;
31 |
32 | private final CounterRepository repository =
33 | new CounterRepository<>(PublisherCounterSet::new);
34 |
35 | @Test
36 | void shouldCreateAndCacheNewCounters()
37 | {
38 | assertThat(repository.getOrCreate(AERON_UDP_LOCALHOST_12355, SESSION_ID, STREAM_ID))
39 | .isNotNull();
40 | assertThat(repository.getOrCreate(AERON_UDP_LOCALHOST_12355, SESSION_ID, STREAM_ID))
41 | .isSameAs(repository.getOrCreate(AERON_UDP_LOCALHOST_12355, SESSION_ID, STREAM_ID));
42 | }
43 | }
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/event/RateMonitorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.event;
19 |
20 | import com.aitusoftware.aether.aggregation.RateBucket;
21 | import com.aitusoftware.aether.aggregation.StreamRate;
22 | import com.aitusoftware.aether.model.ChannelSessionKey;
23 | import com.aitusoftware.aether.model.PublisherCounterSet;
24 | import com.aitusoftware.aether.model.SystemCounters;
25 | import org.junit.jupiter.api.Test;
26 |
27 | import java.util.*;
28 | import java.util.concurrent.TimeUnit;
29 |
30 | import static com.google.common.truth.Truth.assertThat;
31 |
32 | class RateMonitorTest
33 | {
34 | private static final int SESSION_ID = 7;
35 | private static final int STREAM_ID = 11;
36 | private static final String CTX_0 = "first";
37 | private static final String CTX_1 = "second";
38 | private final RateMonitor rateMonitor = new RateMonitor(Arrays.asList(
39 | new RateBucket(10, TimeUnit.SECONDS),
40 | new RateBucket(30, TimeUnit.SECONDS)));
41 |
42 | @Test
43 | void shouldUpdateAndStoreStreamRatesByPublisher()
44 | {
45 | for (int i = 0; i < 30; i++)
46 | {
47 | rateMonitor.onSnapshot(CTX_0, TimeUnit.SECONDS.toMillis(100 + i),
48 | publishers(CTX_0, 1000L + 1000 * i, 2000L + 2000 * i),
49 | Collections.emptyList(), new SystemCounters());
50 | rateMonitor.onSnapshot(CTX_1, TimeUnit.SECONDS.toMillis(100 + i),
51 | publishers(CTX_1, 1000L + 3000 * i, 2000L + 5000 * i),
52 | Collections.emptyList(), new SystemCounters());
53 | }
54 |
55 | final Map publisherRates = rateMonitor.publisherRates();
56 |
57 | assertPublisherRates(publisherRates, 1000L, 1000L, 0, CTX_0);
58 | assertPublisherRates(publisherRates, 2000L, 2000L, 1, CTX_0);
59 | assertPublisherRates(publisherRates, 3000L, 3000L, 0, CTX_1);
60 | assertPublisherRates(publisherRates, 5000L, 5000L, 1, CTX_1);
61 | }
62 |
63 | private void assertPublisherRates(
64 | final Map publisherRates,
65 | final long firstRate, final long secondRate, final int index, final String context)
66 | {
67 | final List rates = new ArrayList<>();
68 | final StreamRate streamRate = publisherRates.get(
69 | new ChannelSessionKey(context, context + "_" + index, STREAM_ID, SESSION_ID));
70 | streamRate.consumeRates((duration, durationUnit, bytesPerSecond) -> rates.add(bytesPerSecond));
71 | assertThat(rates.size()).isEqualTo(2);
72 |
73 | assertThat(rates.get(0)).isEqualTo(firstRate);
74 | assertThat(rates.get(1)).isEqualTo(secondRate);
75 | }
76 |
77 | private List publishers(final String label, final Long... streamPositions)
78 | {
79 | final List publisherCounters = new ArrayList<>();
80 | for (int i = 0; i < streamPositions.length; i++)
81 | {
82 | final Long position = streamPositions[i];
83 | final PublisherCounterSet counterSet = new PublisherCounterSet();
84 | counterSet.reset(label + "_" + i, SESSION_ID, STREAM_ID);
85 | counterSet.publisherPosition(position);
86 | publisherCounters.add(counterSet);
87 | }
88 | return publisherCounters;
89 | }
90 | }
--------------------------------------------------------------------------------
/src/test/java/com/aitusoftware/aether/transport/SnapshotSerialiserTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 Aitu Software Limited.
3 | *
4 | * https://aitusoftware.com
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.aitusoftware.aether.transport;
19 |
20 | import com.aitusoftware.aether.event.CounterSnapshotListener;
21 | import com.aitusoftware.aether.model.SystemCounters;
22 | import com.aitusoftware.aether.model.PublisherCounterSet;
23 | import com.aitusoftware.aether.model.SubscriberCounterSet;
24 | import org.agrona.ExpandableArrayBuffer;
25 | import org.junit.jupiter.api.BeforeEach;
26 | import org.junit.jupiter.api.Test;
27 |
28 | import java.util.Arrays;
29 | import java.util.Collections;
30 | import java.util.List;
31 |
32 | import static com.google.common.truth.Truth.assertThat;
33 |
34 | class SnapshotSerialiserTest
35 | {
36 | private static final String LABEL = "label-0";
37 | private static final long TIMESTAMP = 1234567890333L;
38 | private final SnapshotSerialiser serialiser = new SnapshotSerialiser();
39 | private final SnapshotDeserialiser deserialiser = new SnapshotDeserialiser();
40 | private final SystemCounters systemCounters = new SystemCounters();
41 |
42 | @BeforeEach
43 | void setUp()
44 | {
45 | systemCounters.bytesSent(1);
46 | systemCounters.bytesReceived(2);
47 | systemCounters.naksSent(3);
48 | systemCounters.naksReceived(4);
49 | systemCounters.errors(5);
50 | systemCounters.clientTimeouts(6);
51 | }
52 |
53 | @Test
54 | void shouldConvert()
55 | {
56 | final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();
57 | final int length = serialiser.serialiseSnapshot(LABEL, TIMESTAMP, publishers(), subscribers(),
58 | systemCounters, buffer);
59 | deserialiser.deserialiseSnapshot(buffer, 0, new SnapshotAssertion());
60 | }
61 |
62 | private List subscribers()
63 | {
64 | final SubscriberCounterSet s0 = new SubscriberCounterSet();
65 | s0.reset("chan-1", 2, 7);
66 | s0.receiverHighWaterMark(1234L);
67 | return Collections.singletonList(s0);
68 | }
69 |
70 | private List publishers()
71 | {
72 | final PublisherCounterSet p0 = new PublisherCounterSet();
73 | p0.reset("chan-1", 2, 7);
74 | p0.senderPosition(1234L);
75 | final PublisherCounterSet p1 = new PublisherCounterSet();
76 | p1.reset("chan-2", 5, 11);
77 | p1.publisherLimit(1234L);
78 | return Arrays.asList(p0, p1);
79 | }
80 |
81 | private class SnapshotAssertion implements CounterSnapshotListener
82 | {
83 | @Override
84 | public void onSnapshot(
85 | final String label,
86 | final long timestamp,
87 | final List publisherCounters,
88 | final List subscriberCounters,
89 | final SystemCounters systemCounters)
90 | {
91 | assertThat(label).isEqualTo(LABEL);
92 | assertThat(timestamp).isEqualTo(TIMESTAMP);
93 | assertThat(publisherCounters.size()).isEqualTo(publishers().size());
94 | assertThat(subscriberCounters.size()).isEqualTo(subscribers().size());
95 | assertThat(subscriberCounters.get(0).channel().toString()).isEqualTo("chan-1");
96 | assertThat(subscriberCounters.get(0).receiverHighWaterMark()).isEqualTo(1234L);
97 | assertThat(publisherCounters.get(0).channel().toString()).isEqualTo("chan-1");
98 | assertThat(publisherCounters.get(0).senderPosition()).isEqualTo(1234L);
99 | assertThat(publisherCounters.get(1).channel().toString()).isEqualTo("chan-2");
100 | assertThat(publisherCounters.get(1).publisherLimit()).isEqualTo(1234L);
101 |
102 | assertThat(systemCounters.bytesSent()).isEqualTo((1L));
103 | assertThat(systemCounters.bytesReceived()).isEqualTo((2L));
104 | assertThat(systemCounters.naksSent()).isEqualTo((3L));
105 | assertThat(systemCounters.naksReceived()).isEqualTo((4L));
106 | assertThat(systemCounters.errors()).isEqualTo((5L));
107 | assertThat(systemCounters.clientTimeouts()).isEqualTo((6L));
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/version.txt:
--------------------------------------------------------------------------------
1 | 0.2.2
2 |
--------------------------------------------------------------------------------