Skip to content

Commit

Permalink
Added features to loggers
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaron committed Aug 22, 2023
1 parent 596e7b3 commit 3de9f47
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class MetricsReport(
get() = roboquant.logger

private val charts
get() = logger.metricNames.map {
get() = logger.getMetricNames().map {
{
val data = roboquant.logger.getMetric(it)
val chart = TimeSeriesChart(data)
Expand Down Expand Up @@ -92,7 +92,7 @@ class MetricsReport(


private fun metricsToHTML(): String {
val metricsMap = logger.metricNames.map { it to logger.getMetric(it) }
val metricsMap = logger.getMetricNames().map { it to logger.getMetric(it) }
val result = StringBuffer()
for ((name, metrics) in metricsMap) {
result += "<div class='flex-item'><table frame=void rules=rows class='table'><caption>$name</caption>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ class QuestDBMetricsLogger(dbPath: Path = Config.home / "questdb-metrics" / "db"
engine = CairoEngine(config)
}

/**
* Load previous runs already in the database, so they are accessible via [getMetric]
*/
fun loadPreviousRuns() {
tables.addAll(engine.tables())
}

override fun log(results: Map<String, Double>, time: Instant, run: String) {
if (results.isEmpty()) return
if (! tables.contains(run)) {
Expand All @@ -67,9 +74,12 @@ class QuestDBMetricsLogger(dbPath: Path = Config.home / "questdb-metrics" / "db"

}

override fun getMetric(name: String, run: String): TimeSeries {
/**
* Get a metric for a specific [run]
*/
override fun getMetric(metricName: String, run: String): TimeSeries {
val result = mutableListOf<Observation>()
engine.query("select * from '$run' where metric='$name'") {
engine.query("select * from '$run' where metric='$metricName'") {
while (hasNext()) {
val r = this.record
val o = Observation(ofEpochMicro(r.getTimestamp(1)), r.getDouble(0))
Expand All @@ -79,11 +89,32 @@ class QuestDBMetricsLogger(dbPath: Path = Config.home / "questdb-metrics" / "db"
return TimeSeries(result)
}

/**
* get a specific metric for all runs
*/
override fun getMetric(metricName: String): Map<String, TimeSeries> {
val result = mutableMapOf<String, TimeSeries>()
for (table in tables) {
val v = getMetric(metricName, table)
if (v.isNotEmpty()) result[table] = v
}
return result
}

override fun start(run: String, timeframe: Timeframe) {
// engine.update("drop table $run")
tables.remove(run)
}


override fun getMetricNames(run: String): Set<String> {
return engine.distictSymbol(run, "name").toSortedSet()
}


override val runs: Set<String>
get() = engine.tables().toSet()

private fun createTable(name: String) {
engine.update(
"""CREATE TABLE IF NOT EXISTS '$name' (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ internal inline fun CairoEngine.query(query: String, block: RecordCursor.() -> U
}


internal fun CairoEngine.distictSymbol(tableName: String, column: String) : Set<String>{
SqlExecutionContextImpl(this, 1).use { ctx ->
sqlCompiler.use {
val sql = "SELECT DISTINCT $column from '$tableName"
val fact = it.compile(sql, ctx).recordCursorFactory
fact.use {
val result = mutableSetOf<String>()
fact.getCursor(ctx).use { cursor ->
while(cursor.hasNext()) {
val r= cursor.record
val s = r.getSym(0)
result.add(s.toString())
}
}
return result
}
}
}
}




internal fun CairoEngine.insert(tableName: String, block: TableWriter.() -> Unit) {
SqlExecutionContextImpl(this, 1).use { ctx ->
getWriter(ctx.getTableToken(tableName), tableName).use {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal class QuestDBFeedTest {
val feed1 = RandomWalkFeed(tf, nAssets = 1, template = Asset("ABC"))
val feed2 = RandomWalkFeed(tf, nAssets = 1, template = Asset("XYZ"))

// Need to partition when adding out-of-order price actions
recorder.record<PriceBar>(feed1, "pricebars3", partition = "YEAR")
recorder.record<PriceBar>(feed2, "pricebars3", append = true)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private fun FlowContent.echarts(elemId: String, width: String = "100%", height:
}

private fun FlowContent.metricForm(target: String, run: String, info: RunInfo) {
val metricNames = info.roboquant.logger.metricNames
val metricNames = info.roboquant.logger.getMetricNames()
form {
hxPost = "/echarts"
hxTarget = target
Expand Down
21 changes: 13 additions & 8 deletions roboquant/src/main/kotlin/org/roboquant/loggers/LastEntryLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class LastEntryLogger(var showProgress: Boolean = false) : MetricsLogger {
}
}

override val runs: Set<String>
get() = history.keys

override fun start(run: String, timeframe: Timeframe) {
history.remove(run)
if (showProgress) progressBar.start(run, timeframe)
Expand All @@ -68,27 +71,29 @@ class LastEntryLogger(var showProgress: Boolean = false) : MetricsLogger {
/**
* Get the unique list of metric names that have been captured
*/
override val metricNames: List<String>
get() = history.values.map { it.keys }.flatten().distinct().sorted()
override fun getMetricNames(run: String) : Set<String> {
val values = history[run] ?: return emptySet()
return values.map { it.key }.distinct().toSortedSet()
}

/**
* Get results for the metric specified by its [name].
* Get results for the metric specified by its [metricName].
*/
override fun getMetric(name: String): Map<String, TimeSeries> {
override fun getMetric(metricName: String): Map<String, TimeSeries> {
val result = mutableMapOf<String, TimeSeries>()
for (run in history.keys) {
val ts = getMetric(name, run)
val ts = getMetric(metricName, run)
if (ts.isNotEmpty()) result[run] = ts
}
return result
}

/**
* Get results for the metric specified by its [name].
* Get results for the metric specified by its [metricName].
*/
override fun getMetric(name: String, run: String): TimeSeries {
override fun getMetric(metricName: String, run: String): TimeSeries {
val entries = history[run] ?: return TimeSeries(emptyList())
val v = entries[name]
val v = entries[metricName]
val result = if (v == null) emptyList() else listOf(v)
return TimeSeries(result)
}
Expand Down
20 changes: 11 additions & 9 deletions roboquant/src/main/kotlin/org/roboquant/loggers/MemoryLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,38 @@ class MemoryLogger(var showProgress: Boolean = true) : MetricsLogger {
/**
* Get all the recorded runs in this logger
*/
val runs: Set<String>
override val runs: Set<String>
get() = history.keys.toSortedSet()

/**
* Get the unique list of metric names that have been captured
*/
override val metricNames: List<String>
get() = history.values.asSequence().flatten().map { it.metrics.keys }.flatten().distinct().sorted().toList()
override fun getMetricNames(run: String) : Set<String> {
val values = history[run] ?: return emptySet()
return values.map { it.metrics.keys }.flatten().toSortedSet()
}

/**
* Get results for a metric specified by its [name]. It will include all the runs for that metric.
* Get results for a metric specified by its [metricName]. It will include all the runs for that metric.
*/
override fun getMetric(name: String): Map<String, TimeSeries> {
override fun getMetric(metricName: String): Map<String, TimeSeries> {
val result = mutableMapOf<String, TimeSeries>()
for (run in history.keys) {
val ts = getMetric(name, run)
val ts = getMetric(metricName, run)
if (ts.isNotEmpty()) result[run] = ts
}
return result.toSortedMap()
}

/**
* Get results for a metric specified by its [name] for a single [run]
* Get results for a metric specified by its [metricName] for a single [run]
*/
override fun getMetric(name: String, run: String): TimeSeries {
override fun getMetric(metricName: String, run: String): TimeSeries {
val entries = history[run] ?: return TimeSeries(emptyList())
val values = mutableListOf<Double>()
val times = mutableListOf<Instant>()
entries.forEach {
val e = it.metrics[name]
val e = it.metrics[metricName]
if (e != null) {
values.add(e)
times.add(it.time)
Expand Down
41 changes: 32 additions & 9 deletions roboquant/src/main/kotlin/org/roboquant/loggers/MetricsLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,50 @@ interface MetricsLogger : Lifecycle {
fun log(results: Map<String, Double>, time: Instant, run: String)

/**
* Get all the logged data for a specific metric identified by its [name].
* Get all the logged data for a specific [metricName].
* The result is a Map with the key being the run-name and the value being the [TimeSeries].
*
* This is optional to implement for a MetricsLogger since not all metric-loggers store metrics.
* Use [metricNames] to see which metrics are available.
*/
fun getMetric(name: String): Map<String, TimeSeries> = emptyMap()
fun getMetric(metricName: String): Map<String, TimeSeries> = buildMap {
runs.forEach {
val v = getMetric(metricName,it)
if (v.isNotEmpty()) put(it, v)
}
}

/**
* Get the metric identified by its [name] for a single [run]. The result is a [TimeSeries].
* Get the metric identified by its [metricName] for a single [run].
* The result is a [TimeSeries].
*
* This is optional to implement for a MetricsLogger since not all metric-loggers store metrics.
* Use [metricNames] to see which metrics are available.
* This is optional to implement for a MetricsLogger since not all metric-loggers store metrics.
* Use [getMetricNames] to see which metrics are available.
*/
fun getMetric(metricName: String, run: String): TimeSeries = TimeSeries(emptyList())

/**
* The set of metric names that are available and can be retrieved with the [getMetric].
* This across all runs and can be an extensive operation.
*/
fun getMetricNames(): Set<String> = buildSet {
runs.forEach {
val v = getMetricNames(it)
addAll(v)
}
}


/**
* Get all available metric-names for a certain [run]
*/
fun getMetric(name: String, run: String): TimeSeries = getMetric(name)[run] ?: TimeSeries(emptyList())
fun getMetricNames(run: String): Set<String> = emptySet()

/**
* The list of metric names that are available and can be retrieved with the [getMetric].
* The list of runs that are available and can be retrieved with the [getMetric].
*/
val metricNames: List<String>
get() = emptyList()
val runs: Set<String>
get() = emptySet()

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ internal class LastEntryLoggerTest {

logger.log(metrics, Instant.now(), "test")
logger.end("test")
assertTrue(logger.metricNames.isNotEmpty())
assertContains(logger.metricNames, metrics.keys.first())
assertTrue(logger.getMetricNames().isNotEmpty())
assertContains(logger.getMetricNames(), metrics.keys.first())

val m1 = logger.metricNames.first()
val m1 = logger.getMetricNames().first()
val m = logger.getMetric(m1).latestRun()
assertTrue(m.isNotEmpty())

logger.reset()
assertTrue(logger.metricNames.isEmpty())
assertTrue(logger.getMetricNames().isEmpty())
}

@Test
Expand All @@ -55,9 +55,9 @@ internal class LastEntryLoggerTest {
}

logger.end("test")
assertTrue(logger.metricNames.isNotEmpty())
assertTrue(logger.getMetricNames().isNotEmpty())

val m1 = logger.metricNames.first()
val m1 = logger.getMetricNames().first()
val m = logger.getMetric(m1).latestRun()
assertEquals(m.timeline.sorted(), m.timeline)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ internal class MemoryLoggerTest {
@Test
fun memoryLogger() {
val logger = MemoryLogger(showProgress = false)
assertTrue(logger.metricNames.isEmpty())
assertTrue(logger.getMetricNames().isEmpty())

val metrics = TestData.getMetrics()

logger.start("test", Timeframe.INFINITE)
logger.log(metrics, Instant.now(), "test")
logger.end("test")
assertFalse(logger.metricNames.isEmpty())
assertEquals(metrics.size, logger.metricNames.size)
assertFalse(logger.getMetricNames().isEmpty())
assertEquals(metrics.size, logger.getMetricNames().size)

val t = logger.getMetric(metrics.keys.first()).latestRun()
assertEquals(1, t.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal class SilentLoggerTest {
val logger = SilentLogger()
logger.log(TestData.getMetrics(), Instant.now(), "test")
assertEquals(1, logger.events)
assertTrue(logger.metricNames.isEmpty())
assertTrue(logger.getMetricNames().isEmpty())
assertTrue(logger.getMetric("key1").isEmpty())

logger.log(TestData.getMetrics(), Instant.now(), "test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ internal class SkipWarmupLoggerTest {
repeat(9) {
logger.log(metrics, Instant.now(), "test")
}
assertTrue(logger.metricNames.isEmpty())
assertTrue(logger.getMetricNames().isEmpty())

repeat(4) {
logger.log(metrics, Instant.now(), "test")
}
assertFalse(logger.metricNames.isEmpty())
assertFalse(logger.getMetricNames().isEmpty())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class ReturnsMetricTest {
val feed = RandomWalkFeed.lastYears(2)
val rq = Roboquant(EMAStrategy(), metric, logger = MemoryLogger(showProgress = false))
rq.run(feed)
assertContains(rq.logger.metricNames, "returns.sharperatio")
assertContains(rq.logger.getMetricNames(), "returns.sharperatio")
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal class EMAStrategyTest {
strategy.recording = true
val roboquant = Roboquant(strategy, logger = MemoryLogger(false))
roboquant.run(TestData.feed, name = "test")
val names = roboquant.logger.metricNames
val names = roboquant.logger.getMetricNames()

assertTrue(names.isNotEmpty())
val metrics = roboquant.logger.getMetric(names.first()).latestRun()
Expand Down

0 comments on commit 3de9f47

Please sign in to comment.