Skip to content

Commit 1d887f6

Browse files
authored
Merge pull request #105 from strykeforce/healthcheck-fix
Update healthcheck JSON format
2 parents d6a2148 + 527f3b1 commit 1d887f6

File tree

3 files changed

+148
-30
lines changed

3 files changed

+148
-30
lines changed

src/main/kotlin/org/strykeforce/healthcheck/internal/ReportServer.kt

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.sun.net.httpserver.HttpServer
44
import mu.KotlinLogging
55
import org.strykeforce.healthcheck.HealthCheckCommand
66
import java.io.OutputStream
7+
import java.lang.Exception
78
import java.net.InetSocketAddress
89

910
private val logger = KotlinLogging.logger {}
@@ -26,14 +27,21 @@ class ReportServer(private val healthCheck: RobotHealthCheck) {
2627
}
2728

2829
createContext("/data") { httpExchange ->
29-
val jsonVisitor = JsonVisitor()
30-
jsonVisitor.visit(healthCheck)
3130

3231
httpExchange.responseHeaders.let { headers ->
3332
headers["Content-Type"] = "application/json"
3433
}
3534
httpExchange.sendResponseHeaders(200, 0)
36-
httpExchange.responseBody.use { jsonVisitor.sendHealthCheck(it) }
35+
36+
httpExchange.responseBody.use {
37+
try {
38+
val jsonVisitor = JsonVisitor(it)
39+
jsonVisitor.visit(healthCheck)
40+
} catch (e: Exception) {
41+
logger.error(e) { "error creating healthcheck JSON report" }
42+
}
43+
}
44+
3745
}
3846

3947
start()
@@ -43,7 +51,9 @@ class ReportServer(private val healthCheck: RobotHealthCheck) {
4351

4452
}
4553

46-
class JsonVisitor : HealthCheckVisitor {
54+
class JsonVisitor(outputStream: OutputStream) : HealthCheckVisitor {
55+
56+
private val writer = outputStream.bufferedWriter()
4757

4858
private val meta = mapOf(
4959
"case" to StringBuilder(),
@@ -55,27 +65,26 @@ class JsonVisitor : HealthCheckVisitor {
5565
"duration" to StringBuilder(),
5666
)
5767

58-
private val data = mutableMapOf(
59-
"msec_elapsed" to StringBuilder(),
60-
"talon" to StringBuilder(),
61-
"case" to StringBuilder(),
62-
"voltage" to StringBuilder(),
63-
"position" to StringBuilder(),
64-
"speed" to StringBuilder(),
65-
"supply_current" to StringBuilder(),
66-
"stator_current" to StringBuilder(),
67-
)
6868

6969
private var name = ""
7070

7171
private var metaIndex = 0
7272
private var index = 0
73+
private var isFirst = true
74+
7375
override fun visit(healthCheck: RobotHealthCheck) {
76+
writer.write("{\"data\":[")
7477
healthCheck.healthChecks.forEach { it.accept(this) }
7578

76-
// remove trailing commas, grr json
7779
meta.values.forEach { it.deleteCharAt(it.lastIndex) }
78-
data.values.forEach { it.deleteCharAt(it.lastIndex) }
80+
81+
writer.write("],\"meta\":{")
82+
for ((i, key) in meta.keys.withIndex()) {
83+
writer.write("\"$key\":{${meta[key]}}")
84+
if (i < meta.size - 1) writer.write(",")
85+
}
86+
writer.write("}}")
87+
writer.flush()
7988
}
8089

8190
override fun visit(healthCheck: SubsystemHealthCheck) {
@@ -99,6 +108,17 @@ class JsonVisitor : HealthCheckVisitor {
99108
meta.getValue("duration").append("\"${metaIndex}\":${healthCheck.duration},")
100109
metaIndex++
101110

111+
val data = mapOf(
112+
"msec_elapsed" to StringBuilder(),
113+
"talon" to StringBuilder(),
114+
"case" to StringBuilder(),
115+
"voltage" to StringBuilder(),
116+
"position" to StringBuilder(),
117+
"speed" to StringBuilder(),
118+
"supply_current" to StringBuilder(),
119+
"stator_current" to StringBuilder(),
120+
)
121+
102122
healthCheck.data.forEach { healthCheckData ->
103123
healthCheckData.timestamp.forEachIndexed { i, v ->
104124
data.getValue("msec_elapsed").append("\"${index + i}\":$v,")
@@ -112,24 +132,20 @@ class JsonVisitor : HealthCheckVisitor {
112132
}
113133
index += healthCheckData.timestamp.size
114134
}
115-
}
116-
117-
fun sendHealthCheck(os: OutputStream) {
118-
val writer = os.writer()
119135

120-
writer.write("{\"meta\":{")
121-
for ((i, key) in meta.keys.withIndex()) {
122-
writer.write("\"$key\":{${meta[key]}}")
123-
if (i < meta.size - 1) writer.write(",")
136+
data.values.forEach { if (it.lastIndex > 0) it.deleteCharAt(it.lastIndex) }
137+
if (isFirst) {
138+
writer.write("{")
139+
isFirst = false
140+
} else {
141+
writer.write(",{")
124142
}
125143

126-
writer.write("},\"data\":{")
127144
for ((i, key) in data.keys.withIndex()) {
128145
writer.write("\"$key\":{${data[key]}}")
129146
if (i < data.size - 1) writer.write(",")
130147
}
131-
132-
writer.write("}}")
133-
writer.flush()
148+
writer.write("}")
134149
}
150+
135151
}

src/main/kotlin/org/strykeforce/healthcheck/internal/Visitors.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface HealthCheckVisitor {
1313

1414
class DumpVisitor : HealthCheckVisitor {
1515

16-
private val buffer = StringBuilder()
16+
val buffer = StringBuilder()
1717

1818
private val logger = KotlinLogging.logger {}
1919
override fun visit(healthCheck: RobotHealthCheck) {
@@ -41,7 +41,7 @@ class DumpVisitor : HealthCheckVisitor {
4141
}
4242

4343
override fun visit(healthCheck: TalonHealthCheckCase) {
44-
buffer.appendLine(" $healthCheck")
44+
buffer.appendLine(" $healthCheck: ${healthCheck.data.size} measurements")
4545
healthCheck.data.forEach {
4646
buffer.appendLine(" talon ${it.id} avg. voltage = ${it.averageVoltage.format()} volts")
4747
buffer.appendLine(" talon ${it.id} avg. speed = ${it.averageSpeed.format()} ticks/100ms")
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.strykeforce.healthcheck.internal
2+
3+
import com.ctre.phoenix.motorcontrol.can.BaseTalon
4+
import org.junit.jupiter.api.Assertions.*
5+
import org.junit.jupiter.api.Disabled
6+
import org.junit.jupiter.api.Test
7+
import org.mockito.kotlin.mock
8+
import org.mockito.kotlin.whenever
9+
import java.io.ByteArrayOutputStream
10+
11+
class JsonVisitorTest {
12+
13+
@Test
14+
@Disabled
15+
fun testJson() {
16+
val os = ByteArrayOutputStream()
17+
os.use {
18+
val jsonVisitor = JsonVisitor(it)
19+
jsonVisitor.visit(newRobotHealthCheck())
20+
assertEquals("foo", it.toString())
21+
22+
}
23+
24+
}
25+
26+
@Test
27+
@Disabled
28+
fun testDump() {
29+
val dumpVisitor = DumpVisitor()
30+
dumpVisitor.visit(newRobotHealthCheck())
31+
assertEquals("foo", dumpVisitor.buffer.toString())
32+
}
33+
34+
@Test
35+
@Disabled
36+
fun `RobotHealthCheck contains correct data`() {
37+
val robotHealthCheck = newRobotHealthCheck()
38+
assertEquals(1, robotHealthCheck.healthChecks.size)
39+
val subsystemHealthCheck = robotHealthCheck.healthChecks.first() as SubsystemHealthCheck
40+
assertEquals(1, subsystemHealthCheck.healthChecks.size)
41+
val talonHealthCheck = subsystemHealthCheck.healthChecks.first() as TalonHealthCheck
42+
assertEquals(2, talonHealthCheck.healthChecks.size)
43+
val talonHealthCheckCase = talonHealthCheck.healthChecks.first() as TalonHealthCheckCase
44+
assertEquals(3, talonHealthCheckCase.data.size)
45+
val data = talonHealthCheckCase.data.first()
46+
assertEquals(33, data.deviceId)
47+
assertEquals(20, data.timestamp.first())
48+
assertEquals(3.0, data.voltage.first())
49+
assertEquals(10_000.0, data.position.first())
50+
assertEquals(5_000.0, data.speed.first())
51+
assertEquals(0.75, data.supplyCurrent.first())
52+
assertEquals(1.5, data.statorCurrent.first())
53+
}
54+
}
55+
56+
57+
private fun newRobotHealthCheck(): RobotHealthCheck {
58+
59+
val talon = mock<BaseTalon>()
60+
val case = 0
61+
whenever(talon.deviceID).thenReturn(33)
62+
63+
val data1 = TalonHealthCheckData(0, talon).apply {
64+
timestamp.add(20)
65+
voltage.add(3.0)
66+
position.add(10_000.0)
67+
speed.add(5000.0)
68+
supplyCurrent.add(0.75)
69+
statorCurrent.add(1.5)
70+
}
71+
val data2 = TalonHealthCheckData(0, talon).apply {
72+
timestamp.add(40)
73+
voltage.add(3.1)
74+
position.add(10_00.1)
75+
speed.add(5000.1)
76+
supplyCurrent.add(0.76)
77+
statorCurrent.add(1.6)
78+
}
79+
80+
val case1 = object : TalonHealthCheckCase(talon, false, "TestType", 1.0, 1_000) {
81+
override val name = "Case 1"
82+
override fun isRunning(elapsed: Long) = false
83+
override fun setTalon(talon: BaseTalon) = Unit
84+
}.apply {
85+
data.add(data1)
86+
data.add(data2)
87+
}
88+
89+
val case2 = object : TalonHealthCheckCase(talon, false, "TestType", 1.0, 1_000) {
90+
override val name = "Case 2"
91+
override fun isRunning(elapsed: Long) = false
92+
override fun setTalon(talon: BaseTalon) = Unit
93+
}.apply {
94+
data.add(data1)
95+
data.add(data2)
96+
}
97+
val talonHealthCheck = object : TalonHealthCheck(talon, listOf(case1, case2)) {}
98+
99+
100+
val subsystemHealthCheck = SubsystemHealthCheck("TestSubsystem", listOf(talonHealthCheck))
101+
return RobotHealthCheck("RobotHealthCheck", listOf(subsystemHealthCheck))
102+
}

0 commit comments

Comments
 (0)