Skip to content

Commit 9155353

Browse files
committed
Make imports more time- and memory- efficient.
1 parent 00fef3c commit 9155353

File tree

8 files changed

+113
-43
lines changed

8 files changed

+113
-43
lines changed

codepulse/src/main/scala/com/secdec/codepulse/data/bytecode/CodeForestBuilder.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ class CodeForestBuilder {
4444
}
4545

4646
def result = {
47-
val lb = List.newBuilder[TreeNodeData]
48-
for (root <- roots) root.visitTree { node =>
49-
val id = node.id
50-
val name = node.name
51-
val parentId = node.parentId
52-
val kind = node.kind
53-
val size = node.size
54-
lb += TreeNodeData(id, parentId, name, kind, size)
47+
roots.toStream flatMap { root =>
48+
root.streamTree map { node =>
49+
val id = node.id
50+
val name = node.name
51+
val parentId = node.parentId
52+
val kind = node.kind
53+
val size = node.size
54+
root -> TreeNodeData(id, parentId, name, kind, size)
55+
}
5556
}
56-
lb.result()
5757
}
5858

5959
/** Applies the `condensePathNodes` transformation to each root node

codepulse/src/main/scala/com/secdec/codepulse/data/bytecode/CodeTree2.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ trait CodeTreeNode {
5454
for (child <- children) child.visitTree(callback)
5555
}
5656

57+
/* Stream this node and all of its descendants */
58+
def streamTree(): Stream[CodeTreeNode] = {
59+
this +: (children.toStream.flatMap(_.streamTree))
60+
}
61+
5762
def findChild(predicate: CodeTreeNode => Boolean): Option[CodeTreeNode] = {
5863
children.find(predicate)
5964
}

codepulse/src/main/scala/com/secdec/codepulse/data/trace/TreeNodeData.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ trait TreeNodeDataAccess {
6060
def mapJsp(jspClass: String, nodeId: Int): Unit
6161
def mapJsps(jsps: Iterable[(String, Int)]): Unit = jsps foreach { case (jspPath, nodeId) => mapJsp(jspPath, nodeId) }
6262

63+
case class BulkImportElement(data: TreeNodeData, traced: Option[Boolean])
64+
def bulkImport(data: Iterable[BulkImportElement]): Unit
65+
6366
def storeNode(node: TreeNodeData): Unit
6467
def storeNodes(nodes: Iterable[TreeNodeData]) = nodes foreach storeNode
6568

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Code Pulse: A real-time code coverage testing tool. For more information
3+
* see http://code-pulse.com
4+
*
5+
* Copyright (C) 2014 Applied Visions - http://securedecisions.avi.com
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package com.secdec.codepulse.data.trace
21+
22+
/** Helper utility for mass-importing tree node data into the data model.
23+
*
24+
* @author robertf
25+
*/
26+
class TreeNodeImporter(treeNodeData: TreeNodeDataAccess, bufferSize: Int) {
27+
import treeNodeData.{ BulkImportElement => BufferRecord }
28+
29+
private val buffer = collection.mutable.ListBuffer.empty[BufferRecord]
30+
31+
private def checkAndFlush() {
32+
if (buffer.size >= bufferSize)
33+
flush
34+
}
35+
36+
def flush() {
37+
treeNodeData.bulkImport(buffer)
38+
buffer.clear
39+
}
40+
41+
def +=(data: TreeNodeData) { +=(data, None) }
42+
def +=(data: TreeNodeData, traced: Boolean) { +=(data, Some(traced)) }
43+
def +=(data: TreeNodeData, traced: Option[Boolean]) {
44+
buffer += BufferRecord(data, traced)
45+
checkAndFlush
46+
}
47+
48+
def ++=(data: Iterable[(TreeNodeData, Option[Boolean])]) {
49+
for (chunk <- data.grouped(bufferSize)) {
50+
treeNodeData.bulkImport(chunk map { case (data, traced) => BufferRecord(data, traced) })
51+
}
52+
}
53+
}
54+
55+
object TreeNodeImporter {
56+
def apply(treeNodeData: TreeNodeDataAccess, bufferSize: Int = 2500) = new TreeNodeImporter(treeNodeData, bufferSize)
57+
}

codepulse/src/main/scala/com/secdec/codepulse/data/trace/slick/SlickTreeNodeDataAccess.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ private[slick] class SlickTreeNodeDataAccess(dao: TreeNodeDataDao, db: Database)
9393
dao storeJsps jsps
9494
}
9595

96+
def bulkImport(data: Iterable[BulkImportElement]) = db withTransaction { implicit transaction =>
97+
dao storeNodes data.map(_.data)
98+
dao storeTracedValues data.map { element => element.data.id -> element.traced }
99+
}
100+
96101
def storeNode(node: TreeNodeData) = db withTransaction { implicit transaction =>
97102
dao storeNode node
98103
}

codepulse/src/main/scala/com/secdec/codepulse/data/trace/slick/TreeNodeDataDao.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ private[slick] class TreeNodeDataDao(val driver: JdbcProfile) {
144144

145145
def getTraced()(implicit session: Session) = tracedNodes.list
146146

147+
def storeTracedValues(values: Iterable[(Int, Option[Boolean])])(implicit session: Session) {
148+
tracedNodes ++= values flatMap {
149+
case (id, Some(traced)) => Some(id -> traced)
150+
case _ => None
151+
}
152+
}
153+
147154
def updateTraced(id: Int, traced: Option[Boolean])(implicit session: Session) {
148155
traced match {
149156
case Some(traced) =>

codepulse/src/main/scala/com/secdec/codepulse/tracer/TraceUploadData.scala

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,26 @@
2020
package com.secdec.codepulse.tracer
2121

2222
import java.io.File
23+
24+
import scala.concurrent.ExecutionContext.Implicits.global
25+
import scala.concurrent.Future
26+
27+
import org.apache.commons.io.FilenameUtils
28+
2329
import com.secdec.codepulse.data.bytecode.AsmVisitors
2430
import com.secdec.codepulse.data.bytecode.CodeForestBuilder
31+
import com.secdec.codepulse.data.bytecode.CodeTreeNodeKind
32+
import com.secdec.codepulse.data.jsp.JasperJspAdapter
33+
import com.secdec.codepulse.data.trace.TraceData
2534
import com.secdec.codepulse.data.trace.TraceId
35+
import com.secdec.codepulse.data.trace.TreeNodeData
36+
import com.secdec.codepulse.data.trace.TreeNodeImporter
2637
import com.secdec.codepulse.tracer.export.TraceImporter
27-
import com.secdec.codepulse.util.RichFile.RichFile
2838
import com.secdec.codepulse.util.ZipEntryChecker
39+
2940
import net.liftweb.common.Box
3041
import net.liftweb.common.Failure
31-
import org.apache.commons.io.FilenameUtils
32-
import com.secdec.codepulse.data.jsp.JasperJspAdapter
33-
import com.secdec.codepulse.data.trace.TraceData
34-
import scala.concurrent.Future
35-
import scala.concurrent.ExecutionContext.Implicits.global
3642
import net.liftweb.common.Full
37-
import com.secdec.codepulse.data.bytecode.CodeTreeNodeKind
38-
import com.secdec.codepulse.data.trace.TreeNodeData
3943

4044
object TraceUploadData {
4145

@@ -142,22 +146,20 @@ object TraceUploadData {
142146
if (treemapNodes.isEmpty) {
143147
throw new NoSuchElementException("No method data found in analyzed upload file.")
144148
} else {
145-
val treeNodeData = traceData.treeNodeData
146-
import treeNodeData.ExtendedTreeNodeData
147-
148-
val nodeMap = treemapNodes.map(n => n.id -> n).toMap
149-
def getRoot(n: TreeNodeData): TreeNodeData = n.parentId match {
150-
case Some(parent) => getRoot(nodeMap(parent))
151-
case None => n
149+
val importer = TreeNodeImporter(traceData.treeNodeData)
150+
151+
importer ++= treemapNodes map {
152+
case (root, node) =>
153+
node -> (node.kind match {
154+
case CodeTreeNodeKind.Grp | CodeTreeNodeKind.Pkg => Some(tracedGroups contains root.name)
155+
case _ => None
156+
})
152157
}
153158

154-
treeNodeData.storeNodes(treemapNodes)
155-
for (n <- treemapNodes) n.traced = n.kind match {
156-
case CodeTreeNodeKind.Grp | CodeTreeNodeKind.Pkg => Some(tracedGroups contains getRoot(n).label)
157-
case _ => None
158-
}
159-
treeNodeData.mapJsps(jspCorrelations)
160-
treeNodeData.mapMethodSignatures(methodCorrelations)
159+
importer.flush
160+
161+
traceData.treeNodeData.mapJsps(jspCorrelations)
162+
traceData.treeNodeData.mapMethodSignatures(methodCorrelations)
161163

162164
// The `creationDate` for trace data detected in this manner
163165
// should use its default value ('now'), as this is when the data

codepulse/src/main/scala/com/secdec/codepulse/tracer/export/TraceImportReaderV1.scala

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,7 @@ object TraceImportReaderV1 extends TraceImportReader with TraceImportHelpers wit
7171
if (jp.nextToken != START_ARRAY)
7272
throw new TraceImportException(s"Unexpected token ${jp.getCurrentToken}; expected START_ARRAY.")
7373

74-
case class BufferRecord(data: TreeNodeData, traced: Option[Boolean])
75-
val buffer = collection.mutable.ListBuffer.empty[BufferRecord]
76-
def flushBuffer() {
77-
treeNodeData.storeNodes(buffer.map(_.data))
78-
for (BufferRecord(d, traced) <- buffer) d.traced = traced
79-
buffer.clear
80-
}
81-
def checkAndFlush() { if (buffer.size >= 500) flushBuffer }
74+
val importer = TreeNodeImporter(treeNodeData)
8275

8376
while (jp.nextValue != END_ARRAY) {
8477
if (jp.getCurrentToken != START_OBJECT)
@@ -118,18 +111,16 @@ object TraceImportReaderV1 extends TraceImportReader with TraceImportHelpers wit
118111
}
119112
}
120113

121-
buffer += BufferRecord(TreeNodeData(
114+
importer += (TreeNodeData(
122115
id getOrElse { throw new TraceImportException("Missing ID for tree node.") },
123116
parentId,
124117
label getOrElse { throw new TraceImportException("Missing label for tree node.") },
125118
kind.getOrElse { throw new TraceImportException("Missing or invalid kind for tree node.") },
126119
size),
127120
traced)
128-
129-
checkAndFlush
130121
}
131122

132-
flushBuffer
123+
importer.flush
133124

134125
if (jp.nextToken != null)
135126
throw new TraceImportException(s"Unexpected token ${jp.getCurrentToken}; expected EOF.")

0 commit comments

Comments
 (0)