Skip to content

Commit 001cb0f

Browse files
committed
Add server side filtering of treemap data.
1 parent fae8711 commit 001cb0f

File tree

3 files changed

+74
-17
lines changed

3 files changed

+74
-17
lines changed

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ case class PackageTreeNode(id: Option[Int], kind: CodeTreeNodeKind, label: Strin
1414
* @author robertf
1515
*/
1616
class TreeBuilder(treeNodeData: TreeNodeDataAccess) {
17+
/** Full set of tree roots and nodes */
1718
lazy val (roots, nodes) = {
1819
val roots = List.newBuilder[Int]
1920
val nodes = Map.newBuilder[Int, TreeNodeData]
@@ -39,6 +40,7 @@ class TreeBuilder(treeNodeData: TreeNodeDataAccess) {
3940
(roots.result.map(buildNode), nodeMap)
4041
}
4142

43+
/** Full package tree, with self nodes */
4244
lazy val packageTree = {
4345
// build up a package tree with the relevant data
4446

@@ -85,4 +87,48 @@ class TreeBuilder(treeNodeData: TreeNodeDataAccess) {
8587

8688
roots.map(transform)
8789
}
90+
91+
/** Projects a tree containing the selected packages and their immediate children */
92+
def projectTree(selectedNodes: Set[Int]) = {
93+
val incidentalNodes = collection.mutable.HashSet.empty[Int]
94+
95+
// recursively mark all parents of `selectedNodes` as incidental nodes (partially accepted)
96+
def markIncidentalPath(node: Int) {
97+
incidentalNodes += node
98+
99+
for {
100+
node <- nodes get node
101+
parent <- node.parentId
102+
} markIncidentalPath(parent)
103+
}
104+
105+
selectedNodes.foreach(markIncidentalPath)
106+
107+
// build the projected tree
108+
def filterNode(node: TreeNode): Option[TreeNode] = {
109+
def isSubstantialChild(node: TreeNode) = node.data.kind != CodeTreeNodeKind.Grp && node.data.kind != CodeTreeNodeKind.Pkg
110+
111+
// only include this node if it is incidental
112+
if (incidentalNodes contains node.data.id) {
113+
val isSelected = selectedNodes contains node.data.id
114+
115+
// filter children; don't include substantial data if only incidental
116+
// the only exception to this is if that child was requested specifically
117+
val filteredChildren = node.children.flatMap {
118+
case child if isSubstantialChild(child) =>
119+
if (isSelected) Some(child)
120+
else filterNode(child)
121+
122+
case child => filterNode(child)
123+
}
124+
125+
if (isSelected || !filteredChildren.isEmpty)
126+
Some(TreeNode(node.data, filteredChildren))
127+
else
128+
None
129+
} else None
130+
}
131+
132+
roots.flatMap(filterNode)
133+
}
88134
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@ import scala.concurrent.duration.DurationInt
2626
import scala.language.implicitConversions
2727
import scala.util.Failure
2828
import scala.util.Success
29-
3029
import org.joda.time.format.DateTimeFormat
31-
3230
import com.secdec.codepulse.data.trace._
3331
import com.secdec.codepulse.pages.traces.TraceDetailsPage
34-
3532
import akka.actor.Cancellable
33+
import net.liftweb.common.Full
3634
import net.liftweb.common.Loggable
3735
import net.liftweb.http._
3836
import net.liftweb.http.rest._
@@ -293,9 +291,16 @@ class TraceAPIServer(manager: TraceManager) extends RestHelper with Loggable {
293291
case Paths.PackageTree(target) Get req =>
294292
PackageTreeStreamer.streamPackageTree(target.treeBuilder.packageTree)
295293

296-
// GET the trace's treemap data as json
297-
case Paths.Treemap(target) Get req =>
298-
TreemapDataStreamer.streamTreemapData(target.traceData)
294+
// stream a projected treemap based on the POSTed selected packages
295+
case Paths.Treemap(target) Post req =>
296+
req.param("nodes") match {
297+
case Full(packages) =>
298+
val ids = packages.split(',').flatMap(AsInt.unapply).toSet
299+
val tree = target.treeBuilder.projectTree(ids)
300+
TreemapDataStreamer.streamTreemapData(tree)
301+
302+
case _ => BadResponse()
303+
}
299304

300305
case Paths.TreeInstrumentation(target) Put req =>
301306
def getBool(j: JValue) = j match {

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,33 @@ import net.liftweb.json.Printer
3434
object TreemapDataStreamer {
3535
private val Json = new JsonFactory
3636

37-
private def writeJson(jg: JsonGenerator)(node: TreeNodeData) {
38-
jg writeFieldName node.id.toString
37+
private def writeJson(jg: JsonGenerator)(node: TreeNode) {
3938
jg.writeStartObject
4039

41-
for (parentId <- node.parentId) jg.writeNumberField("parentId", parentId)
42-
jg.writeStringField("name", node.label)
43-
jg.writeStringField("kind", node.kind.label)
44-
for (size <- node.size) jg.writeNumberField("lineCount", size)
45-
for (traced <- node.traced) jg.writeBooleanField("traced", traced)
40+
jg.writeNumberField("id", node.data.id)
41+
for (parentId <- node.data.parentId) jg.writeNumberField("parentId", parentId)
42+
jg.writeStringField("name", node.data.label)
43+
jg.writeStringField("kind", node.data.kind.label)
44+
for (size <- node.data.size) jg.writeNumberField("lineCount", size)
45+
for (traced <- node.data.traced) jg.writeBooleanField("traced", traced)
46+
47+
if (!node.children.isEmpty) {
48+
jg writeArrayFieldStart "children"
49+
node.children.foreach(writeJson(jg))
50+
jg.writeEndArray
51+
}
4652

4753
jg.writeEndObject
4854
}
4955

50-
def streamTreemapData(traceData: TraceData): OutputStreamResponse = {
56+
def streamTreemapData(tree: List[TreeNode]): OutputStreamResponse = {
5157
def writeData(out: OutputStream) {
5258
val jg = Json createGenerator out
5359

5460
try {
55-
jg.writeStartObject
56-
traceData.treeNodeData.foreach(writeJson(jg))
57-
jg.writeEndObject
61+
jg.writeStartArray
62+
tree.foreach(writeJson(jg))
63+
jg.writeEndArray
5864
} finally jg.close
5965
}
6066

0 commit comments

Comments
 (0)