Building the Dashboard#
Steps 6–9 turn the DQL query logic into a versioned Dynatrace dashboard and extend it for production use.
Step 6: Create the Dashboard Manifest#
Create dashboard-file-coverage.yaml in the repository root:
type: dashboard
name: Live Debugger File Coverage - OrderController.java
description: Runtime line-hit coverage from application.snapshots for scattered Live Debugger breakpoints
content:
version: 21
importedWithCode: true
settings: {}
variables: []
layouts:
"0":
x: 0
y: 0
w: 6
h: 4
"1":
x: 6
y: 0
w: 6
h: 4
"2":
x: 0
y: 4
w: 6
h: 5
"3":
x: 6
y: 4
w: 6
h: 5
"4":
x: 0
y: 9
w: 12
h: 6
tiles:
"0":
type: markdown
content: |
# Live Debugger file coverage
This dashboard shows runtime hit coverage for the instrumented lines in `OrderController.java`.
- **Source bucket:** `application.snapshots`
- **Coverage denominator:** 11 scattered breakpoints
- **Coverage dimensions:** `code.filepath`, `code.function`, `code.line.number`
This is runtime execution coverage of the lines you instrumented, not full compiler-grade line coverage.
"1":
title: Coverage %
type: data
query: |
fetch application.snapshots
| filter code.filepath == "OrderController.java"
| summarize covered_lines = countDistinct(code.line.number)
| fieldsAdd target_lines = 11
| fieldsAdd coverage_pct = round(100.0 * covered_lines / target_lines, decimals: 2)
| fields `Coverage %` = concat(toString(coverage_pct), "%")
visualization: singleValue
visualizationSettings:
singleValue:
labelMode: none
recordField: Coverage %
isIconVisible: false
alignment: start
trend:
isVisible: false
isRelative: false
autoSelectVisualization: false
querySettings:
maxResultRecords: 1000
defaultScanLimitGbytes: 500
maxResultMegaBytes: 1
defaultSamplingRatio: 10
enableSampling: false
davis:
enabled: false
davisVisualization:
isAvailable: true
"2":
title: Covered lines by function
type: data
query: |
fetch application.snapshots
| filter code.filepath == "OrderController.java"
| summarize covered_lines = countDistinct(code.line.number), total_hits = count(), by: { code.function }
| sort covered_lines desc, total_hits desc
| fields `Function` = code.function, `Covered lines` = covered_lines, `Hits` = total_hits
visualization: table
visualizationSettings:
table:
hideColumnsForLargeResults: false
autoSelectVisualization: false
querySettings:
maxResultRecords: 1000
defaultScanLimitGbytes: 500
maxResultMegaBytes: 100
defaultSamplingRatio: 10
enableSampling: false
davis:
enabled: false
davisVisualization:
isAvailable: true
"3":
title: Line hit map
type: data
query: |
fetch application.snapshots
| filter code.filepath == "OrderController.java"
| fieldsAdd line = toLong(code.line.number)
| summarize hits = count(), by: { code.function, line }
| sort code.function asc, line asc
| fields `Function` = code.function, `Line` = line, `Hits` = hits
visualization: table
visualizationSettings:
table:
hideColumnsForLargeResults: false
autoSelectVisualization: false
querySettings:
maxResultRecords: 1000
defaultScanLimitGbytes: 500
maxResultMegaBytes: 100
defaultSamplingRatio: 10
enableSampling: false
davis:
enabled: false
davisVisualization:
isAvailable: true
"4":
title: Hot lines
type: data
query: |
fetch application.snapshots
| filter code.filepath == "OrderController.java"
| fieldsAdd line = toLong(code.line.number)
| summarize hits = count(), traces = countDistinct(trace.id), by: { line, code.function }
| sort hits desc
| limit 50
| fields `Line` = line, `Function` = code.function, `Hits` = hits, `Traces` = traces
visualization: table
visualizationSettings:
table:
hideColumnsForLargeResults: false
autoSelectVisualization: false
querySettings:
maxResultRecords: 1000
defaultScanLimitGbytes: 500
maxResultMegaBytes: 100
defaultSamplingRatio: 10
enableSampling: false
davis:
enabled: false
davisVisualization:
isAvailable: true
A few practical notes:
- The markdown tile is useful for documenting the denominator directly in the dashboard.
- The most important tile is the summary query with
coverage_pct. - If you change the scatter set later, update the hard-coded denominator in both the markdown tile and the summary query.
Step 7: Apply the Dashboard with dtctl#
Preview first if you want:
dtctl apply -f dashboard-file-coverage.yaml --dry-run
Then create or update the dashboard:
dtctl apply -f dashboard-file-coverage.yaml
If you already created an earlier invalid draft of the dashboard, delete it first so you do not end up with two dashboards of the same name:
dtctl delete dashboard "Live Debugger File Coverage - OrderController.java" -y
dtctl create dashboard -f dashboard-file-coverage.yaml
Next navigate to your Dynatrace environment and open up the Dashboards app.
You should have a dashboard that looks something like this:

Step 8: Tighten the Dashboard to One Source File#
The examples above filter on code.filepath, code.function, and code.line.number.
That is enough to create a clean file-level coverage board.
If you want to narrow further, add filters such as:
dt.entity.process_group_instancek8s.namespace.namedt.entity.servicetrace.id- Timeframe restrictions
Example:
fetch application.snapshots
| filter code.filepath == "OrderController.java"
| filter k8s.namespace.name == "easytrade"
| filter timestamp > now() - 2h
| summarize hits = count(), by: { code.function, code.line.number }
That turns the dashboard from "all observed runtime hits" into "hits for this file in this workload and this time window".
Step 9: Treat the Scatter Set as Declarative Instrumentation#
Once this pattern works, stop thinking of breakpoints as one-off interactive debugging actions. Think of them as a declarative instrumentation set.
A simple workflow looks like this:
- Generate
breakpoint-lines.txt - Apply the scatter set with a loop
- Drive traffic through the service
- Read coverage from
application.snapshots - Publish a dashboard with a known denominator
- Refine the scatter set and re-run
That makes Live Debugger useful not only for root-cause inspection, but also for runtime verification.
Cleanup#
Remove the breakpoint scatter set when you are done:
dtctl delete breakpoint --all -y
If you want to remove only specific lines:
while read -r line; do
dtctl delete breakpoint "${FILE}:${line}" -y
done < breakpoint-lines.txt
You can also delete the dashboard later:
dtctl delete dashboard "Live Debugger File Coverage - OrderController.java" -y
Your Feedback
We're always looking for ways to improve your experience.
Please take 5 seconds to send feedback via the button on the bottom right of the screen.