Skip to content

Commit af09430

Browse files
committed
JPERF-1106: Gather Apache2 logs
1 parent ae5cd4b commit af09430

File tree

9 files changed

+187
-13
lines changed

9 files changed

+187
-13
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ Dropping a requirement of a major version of a dependency is a new contract.
2323
## [Unreleased]
2424
[Unreleased]: https://github.com/atlassian/aws-infrastructure/compare/release-3.0.0...master
2525

26+
## Added
27+
- Add `DiagnosableLoadBalancer`.
28+
- Add ability to `gatherEvidence` from `ApacheProxyLoadBalancer` by making it `DiagnosableLoadBalancer`.
29+
- Make it possible to specify `extraMeasurementSources` inside `Jira`, so that it's possible to `gatherResults` from any of Jira hosted integrations, e.g. load balancer or plugins.
30+
- Let `Jira` produced by `DataCenterFormula` gather logs of `ApacheProxyLoadBalancer`. Resolve [JPERF-1106].
31+
- Make `StartedNode` a `MeasurementSource`.
32+
33+
## Fixed
34+
- Let every `MeasurementSource` inside `Jira` finish its gathering even if one of them fails. Fix [JPERF-1114].
35+
36+
[JPERF-1106]: https://ecosystem.atlassian.net/browse/JPERF-1106
37+
[JPERF-1114]: https://ecosystem.atlassian.net/browse/JPERF-1114
38+
2639
## [3.0.0] - 2023-05-25
2740
[3.0.0]: https://github.com/atlassian/aws-infrastructure/compare/release-2.29.0...release-3.0.0
2841

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.atlassian.performance.tools.awsinfrastructure
2+
3+
internal class FailSafeRunnable(
4+
private val delegates: Iterable<Runnable>
5+
) : Runnable {
6+
override fun run() {
7+
val exceptions = delegates.mapNotNull {
8+
try {
9+
it.run()
10+
null
11+
} catch (e: Exception) {
12+
e
13+
}
14+
}
15+
16+
when {
17+
exceptions.isEmpty() -> return
18+
exceptions.size == 1 -> throw exceptions[0]
19+
else -> {
20+
val root = Exception("Multiple exceptions were thrown and are added suppressed into this one")
21+
exceptions.forEach { root.addSuppressed(it) }
22+
throw root
23+
}
24+
}
25+
}
26+
}

src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/DataCenterFormula.kt

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.atlassian.performance.tools.awsinfrastructure.api.hardware.M4ExtraLar
1111
import com.atlassian.performance.tools.awsinfrastructure.api.hardware.Volume
1212
import com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer.ApacheEc2LoadBalancerFormula
1313
import com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer.ApacheProxyLoadBalancer
14+
import com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer.DiagnosableLoadBalancer
1415
import com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer.LoadBalancerFormula
1516
import com.atlassian.performance.tools.awsinfrastructure.api.network.Network
1617
import com.atlassian.performance.tools.awsinfrastructure.api.network.NetworkFormula
@@ -19,6 +20,7 @@ import com.atlassian.performance.tools.awsinfrastructure.jira.DataCenterNodeForm
1920
import com.atlassian.performance.tools.awsinfrastructure.jira.DiagnosableNodeFormula
2021
import com.atlassian.performance.tools.awsinfrastructure.jira.StandaloneNodeFormula
2122
import com.atlassian.performance.tools.awsinfrastructure.jira.home.SharedHomeFormula
23+
import com.atlassian.performance.tools.awsinfrastructure.loadbalancer.LoadBalancerMeasurementSource.Extension.asMeasurementSource
2224
import com.atlassian.performance.tools.concurrency.api.AbruptExecutorService
2325
import com.atlassian.performance.tools.concurrency.api.submitWithLogContext
2426
import com.atlassian.performance.tools.infrastructure.api.app.Apps
@@ -326,6 +328,8 @@ class DataCenterFormula private constructor(
326328
loadBalancer.waitUntilHealthy(Duration.ofMinutes(5))
327329
}
328330

331+
val loadBalancerResultsSource = (provisionedLoadBalancer.loadBalancer as? DiagnosableLoadBalancer)
332+
?.asMeasurementSource(resultsTransport.location)
329333
val jira = Jira.Builder(
330334
nodes = nodes,
331335
jiraHome = RemoteLocation(
@@ -336,6 +340,7 @@ class DataCenterFormula private constructor(
336340
address = loadBalancer.uri
337341
)
338342
.jmxClients(jiraNodes.mapIndexed { i, node -> configs[i].remoteJmx.getClient(node.publicIpAddress) })
343+
.extraMeasurementSources(listOfNotNull(loadBalancerResultsSource))
339344
.build()
340345

341346
logger.info("$jira is set up, will expire ${jiraStack.expiry}")

src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/Jira.kt

+30-9
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,49 @@
11
package com.atlassian.performance.tools.awsinfrastructure.api.jira
22

33
import com.atlassian.performance.tools.awsinfrastructure.api.CustomDatasetSource
4+
import com.atlassian.performance.tools.awsinfrastructure.FailSafeRunnable
45
import com.atlassian.performance.tools.awsinfrastructure.api.RemoteLocation
56
import com.atlassian.performance.tools.concurrency.api.submitWithLogContext
67
import com.atlassian.performance.tools.infrastructure.api.MeasurementSource
78
import com.atlassian.performance.tools.infrastructure.api.jvm.jmx.JmxClient
8-
import com.atlassian.performance.tools.jvmtasks.api.TaskTimer.time
99
import com.google.common.util.concurrent.ThreadFactoryBuilder
1010
import org.apache.logging.log4j.LogManager
1111
import org.apache.logging.log4j.Logger
1212
import java.net.URI
1313
import java.util.concurrent.Executors
1414

15+
/**
16+
* @param extraMeasurementSources source of results/diagnostics of: reverse proxy, Crowd, LDAP, DVCS, DB, Jira plugins
17+
* or anything that can be integrated with Jira as part of web application provisioning
18+
*/
1519
class Jira private constructor(
1620
private val nodes: List<StartedNode>,
1721
val jiraHome: RemoteLocation,
1822
val database: RemoteLocation,
1923
val address: URI,
20-
val jmxClients: List<JmxClient> = emptyList()
24+
val jmxClients: List<JmxClient>,
25+
private val extraMeasurementSources: List<MeasurementSource>
2126
) : MeasurementSource {
2227
private val logger: Logger = LogManager.getLogger(this::class.java)
2328

2429
override fun gatherResults() {
25-
if (nodes.isEmpty()) {
26-
logger.warn("No Jira nodes known to JPT, not downloading node results")
30+
val firstNode = nodes.firstOrNull()
31+
val measurementSources = extraMeasurementSources + nodes + listOfNotNull(firstNode?.let { AnalyticsLogsSource(it) })
32+
if (measurementSources.isEmpty()) {
33+
logger.warn("No result sources known to Jira, can't download anything")
2734
return
2835
}
2936
val executor = Executors.newFixedThreadPool(
30-
nodes.size.coerceAtMost(4),
37+
measurementSources.size.coerceAtMost(4),
3138
ThreadFactoryBuilder()
3239
.setNameFormat("results-gathering-thread-%d")
3340
.build()
3441
)
35-
nodes.map { executor.submitWithLogContext("gather $it") { it.gatherResults() } }
36-
.forEach { it.get() }
37-
time("gather analytics") { nodes.firstOrNull()?.gatherAnalyticLogs() }
42+
FailSafeRunnable(
43+
measurementSources.map { executor.submitWithLogContext("gather $it") { it.gatherResults() } }
44+
.map { Runnable { it.get() } }
45+
).run()
46+
3847
executor.shutdownNow()
3948
}
4049

@@ -55,14 +64,26 @@ class Jira private constructor(
5564
private val address: URI
5665
) {
5766
private var jmxClients: List<JmxClient> = emptyList()
67+
private var extraMeasurementSources: List<MeasurementSource> = emptyList()
68+
5869
fun jmxClients(jmxClients: List<JmxClient>) = apply { this.jmxClients = jmxClients }
70+
fun extraMeasurementSources(extraMeasurementSources: List<MeasurementSource>) = apply { this.extraMeasurementSources = extraMeasurementSources }
5971

6072
fun build() = Jira(
6173
nodes = nodes,
6274
jiraHome = jiraHome,
6375
database = database,
6476
address = address,
65-
jmxClients = jmxClients
77+
jmxClients = jmxClients,
78+
extraMeasurementSources = extraMeasurementSources
6679
)
6780
}
81+
82+
private class AnalyticsLogsSource(
83+
private val node: StartedNode
84+
) : MeasurementSource {
85+
override fun gatherResults() {
86+
node.gatherAnalyticLogs()
87+
}
88+
}
6889
}

src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/StartedNode.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.atlassian.performance.tools.awsinfrastructure.api.jira
22

33
import com.atlassian.performance.tools.aws.api.Storage
44
import com.atlassian.performance.tools.awsinfrastructure.api.aws.AwsCli
5+
import com.atlassian.performance.tools.infrastructure.api.MeasurementSource
56
import com.atlassian.performance.tools.infrastructure.api.jira.JiraGcLog
67
import com.atlassian.performance.tools.infrastructure.api.process.RemoteMonitoringProcess
78
import com.atlassian.performance.tools.ssh.api.Ssh
@@ -15,10 +16,10 @@ class StartedNode(
1516
private val jiraPath: String,
1617
private val monitoringProcesses: List<RemoteMonitoringProcess>,
1718
private val ssh: Ssh
18-
) {
19+
) : MeasurementSource {
1920
private val resultsDirectory = "results"
2021

21-
fun gatherResults() {
22+
override fun gatherResults() {
2223
ssh.newConnection().use { shell ->
2324
monitoringProcesses.forEach { it.stop(shell) }
2425
val nodeResultsDirectory = "$resultsDirectory/'$name'"

src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/loadbalancer/ApacheProxyLoadBalancer.kt

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer
22

3+
import com.atlassian.performance.tools.aws.api.StorageLocation
4+
import com.atlassian.performance.tools.awsinfrastructure.api.aws.AwsCli
35
import com.atlassian.performance.tools.infrastructure.api.Sed
4-
import com.atlassian.performance.tools.infrastructure.api.loadbalancer.LoadBalancer
56
import com.atlassian.performance.tools.jvmtasks.api.ExponentialBackoff
67
import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction
78
import com.atlassian.performance.tools.ssh.api.Ssh
@@ -14,7 +15,7 @@ class ApacheProxyLoadBalancer private constructor(
1415
private val ssh: Ssh,
1516
ipAddress: String,
1617
httpPort: Int
17-
) : LoadBalancer {
18+
) : DiagnosableLoadBalancer {
1819

1920
override val uri: URI = URI("http://${ipAddress}:$httpPort/")
2021

@@ -81,6 +82,15 @@ class ApacheProxyLoadBalancer private constructor(
8182
connection.execute("echo \"$line\" | sudo tee -a $APACHE_CONFIG_PATH")
8283
}
8384

85+
override fun gatherEvidence(location: StorageLocation) {
86+
ssh.newConnection().use { connection ->
87+
val resultsDir = "/tmp/s3-results"
88+
connection.execute("mkdir -p $resultsDir")
89+
connection.execute("cp -R /var/log/apache2 $resultsDir")
90+
AwsCli().upload(location, connection, resultsDir, Duration.ofMinutes(1))
91+
}
92+
}
93+
8494
class Builder(
8595
private val ssh: Ssh
8696
) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer
2+
3+
import com.atlassian.performance.tools.aws.api.StorageLocation
4+
import com.atlassian.performance.tools.infrastructure.api.loadbalancer.LoadBalancer
5+
6+
interface DiagnosableLoadBalancer : LoadBalancer {
7+
fun gatherEvidence(location: StorageLocation)
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.atlassian.performance.tools.awsinfrastructure.loadbalancer
2+
3+
import com.atlassian.performance.tools.aws.api.StorageLocation
4+
import com.atlassian.performance.tools.awsinfrastructure.api.loadbalancer.DiagnosableLoadBalancer
5+
import com.atlassian.performance.tools.infrastructure.api.MeasurementSource
6+
7+
internal class LoadBalancerMeasurementSource(
8+
private val loadBalancer: DiagnosableLoadBalancer,
9+
private val target: StorageLocation
10+
) : MeasurementSource {
11+
internal object Extension {
12+
fun DiagnosableLoadBalancer.asMeasurementSource(
13+
target: StorageLocation
14+
) = LoadBalancerMeasurementSource(
15+
loadBalancer = this,
16+
target = target
17+
)
18+
}
19+
20+
override fun gatherResults() {
21+
loadBalancer.gatherEvidence(target)
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.atlassian.performance.tools.awsinfrastructure
2+
3+
import org.assertj.core.api.Assertions.assertThat
4+
import org.junit.Test
5+
6+
class FailSafeRunnableTest {
7+
@Test
8+
fun shouldExecuteAllEvenIfFirstFails() {
9+
var executed1 = false
10+
var executed2 = false
11+
var executed3 = false
12+
val runnable = FailSafeRunnable(
13+
listOf(
14+
Runnable { executed1 = true; throw Exception("Fail 1") },
15+
Runnable { executed2 = true; throw Exception("Fail 2") },
16+
Runnable { executed3 = true; throw Exception("Fail 3") }
17+
)
18+
)
19+
20+
try {
21+
runnable.run()
22+
} catch (e: Exception) {
23+
// Expected and ignored, so that we can go to asserts
24+
}
25+
26+
assertThat(executed1).isTrue()
27+
assertThat(executed2).isTrue()
28+
assertThat(executed3).isTrue()
29+
}
30+
31+
@Test
32+
fun shouldThrowAllFailures() {
33+
val runnable = FailSafeRunnable(
34+
listOf(
35+
Runnable { throw Exception("Banana") },
36+
Runnable { throw Exception("Apple") },
37+
Runnable { throw Exception("Pear") },
38+
Runnable { throw Exception("Peach") }
39+
)
40+
)
41+
42+
val exception = try {
43+
runnable.run()
44+
null
45+
} catch (e: Exception) {
46+
e
47+
}
48+
49+
val allExceptions = listOf(exception!!) + exception.suppressed.toList()
50+
val allMessages = allExceptions.map { it.message }
51+
assertThat(allMessages).contains("Banana", "Apple", "Pear", "Peach")
52+
}
53+
54+
55+
@Test
56+
fun shouldExecuteAll() {
57+
val allIndexes = Array(20) { it }
58+
val finishedIndexes = mutableListOf<Int>()
59+
val runnable = FailSafeRunnable(
60+
allIndexes.map { index -> Runnable { finishedIndexes.add(index) } }
61+
)
62+
63+
runnable.run()
64+
65+
assertThat(finishedIndexes).contains(*allIndexes)
66+
}
67+
}

0 commit comments

Comments
 (0)