-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSatalyzerUse.scala
242 lines (139 loc) · 7.04 KB
/
SatalyzerUse.scala
1
2
3
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/**
Include this file in your Scala solver code to allow writing Satalyzer event logs (JSON).
Modify line import [...].stats (see below)
Initialize the logger by assigning 'stats' a value and calling initializeStatsFile. E.g.,
stats = new Stats(problemFile = "eventLog.json") // various other parameters exist, see method
stats.initializeStatsFile()
To emit an event, use stats.writeEntry(). Examples:
stats.writeEntry(key = "numberUnassigned", value = noOfUnassignedVars, solverThreadNo = 5)
stats.writeEntry(key = "overallTimeMs", value = timeMs, solverThreadNo = 0)
To actually write the collected events to the file, use
stats.writeToFile()
External dependencies: jsoniter (https://jsoniter.com/)
(But changing the code to use some other JSON serialization library, if needed,
should be fairly simple)
*/
package utils
import java.io.{BufferedOutputStream, File, FileOutputStream, OutputStream}
import java.time.Instant
import java.util
import java.util.UUID
import com.jsoniter.output.JsonStream
import [...].stats // replace [...] with the path to a variable 'stats' of type Stats. E.g.,
// var stats = new Stats(problemFile = "solverRunEvents.json")
import sharedDefs._
import utils.Various._
// For visualization of the serialized runtime statistics events, use Satalyzer (separate project)
final case class StatsEntry( // Any modification of this class (e.g., new fields or field names) need to be reflected in the
// deserialization method also (can't be don't automatically).
// Also, of course, class StatsEntry needs to be identical with StatsEntry in project Satalyzer/StatsVisualizer.
messageTimeStamp: Long = -1l, // in nano secs from program start (not to be used as a unique entry or message key)
threadNo: Int = 0,
key: String = null,
valueStr: String = null,
@transient runIDTransient: String = null
) {
}
final case class Stats( // Any modification of this class (e.g., new fields or field names) need to be reflected in the
// deserialization method also (can't be don't automatically).
// Also, of course, class Stats needs to be identical with Stats in project Satalyzer/StatsVisualizer.
problemFile: String,
runID: String = UUID.randomUUID().toString, // a type-4 UUID represented as a string
runStartTimeMs: Long = Instant.now.toEpochMilli(),
// any other information, including solving duration, should be written as StatsEntry entries
entries: util.List[StatsEntry] /*doesn't serialize properly: ArrayBuffer[StatsEntry]*/ =
new util.ArrayList[StatsEntry](),
@transient var outFile: File = null,
@transient var statsFileStream: OutputStream = null,
@transient var lastFlush: Long = 0l
) {
def initializeStatsFile(): Unit = {
val outFileName = "stats_" + toString.replaceAll("[^\\w.-]", "_") + "(UTC).json"
val dir = new File(writeStatsDirectory)
outFile = new File(dir, outFileName)
}
@inline def initializeStatsFileStream(): Unit = {
assert(outFile != null)
statsFileStream = new BufferedOutputStream(
new FileOutputStream(outFile))
}
/** This writes the entire stats to file, replacing any existing file with the respective name */
@inline def writeToFile(): Unit = {
if (writeRuntimeStatsToFile) { // for performance reasons, this condition should ideally be checked already
// before calling this method
initializeStatsFileStream()
try {
JsonStream.serialize(stats, statsFileStream) // closes stream!
} catch { // TODO: probable reason: Graal 20 native image not working with Jsoniter
case e: Exception => delSAT.stomp(-10000, "Cannot serialize runtime stats: " + e + "\nIf you are using a native image executable, it is recommended to try writeRuntimeStatsToFile() with a JVM instead")
}
}
}
/** Don't use this for frequent writing such as logging. For long period statistics gathering only.
*/
@inline def writeEntry(key: String, value: scala.Any,
solverThreadNo: Int = 0 /*0: outside SAT solver thread*/ ,
replace: Boolean = false): Unit = {
if (writeRuntimeStatsToFile) { // for performance reasons, this condition should ideally be checked already
// before calling writeEntry
val messageTimeStampRelativeToProgStartNs = (Instant.now.toEpochMilli() - runStartTimeMs) * 1000000l // TODO
val valueStr = value.toString()
val newStatsEntry = new StatsEntry(messageTimeStamp = messageTimeStampRelativeToProgStartNs,
threadNo = solverThreadNo, key = key, valueStr = valueStr,
runIDTransient = runID)
//println(statsEntry)
this.synchronized {
assert(newStatsEntry != null)
if (replace) { // (costly, but rarely used and for deserialization-related reasons we shouldn't use a map here anyway)
var i = stats.entries.size() - 1
while (i >= 0) {
val existingEntry = stats.entries.get(i)
if (existingEntry.key == key) {
stats.entries.set(i, newStatsEntry)
i = -2
} else
i -= 1
}
if (i == -2)
writeToFile() // since otherwise any existing serialization would be "outdated" (containing the old value of the unique key)
else
entries.add(newStatsEntry)
} else
entries.add(newStatsEntry)
val currentTimerNs = System.nanoTime()
if (currentTimerNs - lastFlush > flushRuntimeStatsEverySec /*sec*/ * 1000000000l) {
lastFlush = currentTimerNs
writeToFile()
}
}
}
}
def getFirstOpt(key: String): Option[StatsEntry] = {
var i = 0
while (i < entries.size()) {
val existingEntry = entries.get(i)
if (existingEntry.key == key)
return Some(existingEntry)
i += 1
}
None
}
def countEntriesWithKey(key: String, maxCount: Int = Int.MaxValue): Int = {
var count = 0
var i = 0
while (i < entries.size()) {
if (entries.get(i).key == key) {
count += 1
if (count >= maxCount)
return count
}
i += 1
}
count
}
override def toString(): String = {
val runStartTimeMsStr = Instant.ofEpochMilli(runStartTimeMs).toString
val problemName = fileNameFromFullPath(problemFile)
problemName + " [" + runStartTimeMsStr + "]"
}
}