github.com/grafana/pyroscope@v1.18.0/pkg/operations/v2/tool.blocks.profile.call.tree.gohtml (about) 1 <!DOCTYPE html> 2 <html class="h-100" data-bs-theme="dark"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 8 <title>Bucket Blocks Explorer (v2): Profile Call Tree</title> 9 10 <link rel="stylesheet" href="/static/bootstrap-5.3.3.min.css"> 11 <link rel="stylesheet" href="/static/bootstrap-icons-1.8.1.css"> 12 <link rel="stylesheet" href="/static/pyroscope-styles.css"> 13 <script src="/static/bootstrap-5.3.3.bundle.min.js"></script> 14 15 <style> 16 .tree-view { 17 font-family: 'Courier New', Courier, monospace; 18 font-size: 14px; 19 line-height: 1.5; 20 } 21 .tree-node { 22 margin: 0 !important; 23 list-style-type: none; 24 padding: 0 !important; 25 } 26 .tree-node > li { 27 margin: 0 !important; 28 padding: 0 !important; 29 line-height: 1.5 !important; 30 } 31 .tree-toggle { 32 cursor: pointer; 33 user-select: none; 34 color: #6c757d; 35 margin-right: 5px; 36 font-family: monospace; 37 display: inline-block; 38 width: 35px; 39 text-align: left; 40 white-space: nowrap; 41 } 42 .tree-toggle:hover { 43 color: #adb5bd; 44 } 45 .tree-content { 46 display: flex; 47 align-items: baseline; 48 gap: 0; 49 margin: 0; 50 padding: 0; 51 width: 100%; 52 } 53 .tree-name-section { 54 width: 50%; 55 min-width: 0; 56 margin: 0; 57 padding: 0; 58 } 59 .tree-name-section.clickable { 60 cursor: pointer; 61 } 62 .tree-name { 63 color: #0dcaf0; 64 font-weight: 500; 65 } 66 .tree-value { 67 color: #ffc107; 68 text-align: right; 69 white-space: nowrap; 70 width: 5%; 71 flex-shrink: 0; 72 margin: 0; 73 padding: 0; 74 } 75 .tree-percent { 76 color: #6c757d; 77 text-align: right; 78 white-space: nowrap; 79 width: 5%; 80 flex-shrink: 0; 81 margin: 0 0 0 10px; 82 padding: 0; 83 } 84 .tree-extra { 85 width: 40%; 86 flex-shrink: 0; 87 margin: 0 0 0 15px; 88 padding: 0; 89 font-size: 12px; 90 overflow: hidden; 91 text-overflow: ellipsis; 92 white-space: nowrap; 93 } 94 .tree-children { 95 margin-left: 0 !important; 96 padding: 0 !important; 97 } 98 .tree-children .tree-name-section { 99 padding-left: 20px; 100 } 101 .tree-children .tree-children .tree-name-section { 102 padding-left: 40px; 103 } 104 .tree-children .tree-children .tree-children .tree-name-section { 105 padding-left: 60px; 106 } 107 .tree-children .tree-children .tree-children .tree-children .tree-name-section { 108 padding-left: 80px; 109 } 110 .tree-children .tree-children .tree-children .tree-children .tree-children .tree-name-section { 111 padding-left: 100px; 112 } 113 .tree-children .tree-children .tree-children .tree-children .tree-children .tree-children .tree-name-section { 114 padding-left: 120px; 115 } 116 </style> 117 </head> 118 <body class="d-flex flex-column h-100"> 119 <main class="flex-shrink-0"> 120 <div class="container-fluid"> 121 <div class="header row border-bottom py-3 flex-column-reverse flex-sm-row"> 122 <div class="col-12 col-sm-9 text-center text-sm-start"> 123 <h3>Bucket Blocks Explorer (v2): Profile Call Tree</h3> 124 </div> 125 <div class="col-12 col-sm-3 text-center text-sm-end mb-3 mb-sm-0"> 126 <a href="/ops/object-store/tenants"> 127 <img alt="Pyroscope logo" class="pyroscope-brand" src="/static/pyroscope-logo.png"> 128 </a> 129 </div> 130 </div> 131 <div class="row my-3"> 132 <p> 133 <a href="/ops/object-store/tenants/{{ .User }}/blocks/{{ .BlockID }}/datasets/profiles?dataset={{ if .Dataset.Name }}{{ .Dataset.Name }}{{ else }}_empty{{ end }}&shard={{ .Shard }}&block_tenant={{ .BlockTenant }}">Back to profiles</a> 134 </p> 135 136 <div class="row mb-3"> 137 <div class="col-md-6"> 138 <div class="card bg-dark border-secondary info-card h-100"> 139 <div class="card-header"> 140 <h5 class="mb-0">Dataset Information</h5> 141 </div> 142 <div class="card-body"> 143 <ul> 144 <li>Dataset Name: {{ if .Dataset.Name }}{{ .Dataset.Name }}{{ else }}<em>(empty)</em>{{ end }}</li> 145 <li>Dataset Tenant: {{ .Dataset.Tenant }}</li> 146 <li>Block ID: {{ .BlockID }}</li> 147 <li>Shard: {{ .Shard }}</li> 148 </ul> 149 </div> 150 </div> 151 </div> 152 153 {{ if .ProfileInfo }} 154 <div class="col-md-6"> 155 <div class="card bg-dark border-secondary info-card h-100"> 156 <div class="card-header"> 157 <h5 class="mb-0">Profile Information</h5> 158 </div> 159 <div class="card-body py-2"> 160 <div class="row mb-2"> 161 <div class="col-12"> 162 <strong class="text-muted">Profile Type:</strong> 163 <span class="ms-2"> 164 <code class="text-primary fs-6">{{ .ProfileInfo.ProfileType }}</code> 165 </span> 166 </div> 167 </div> 168 <div class="row mb-2"> 169 <div class="col-12"> 170 <strong class="text-muted">Timestamp:</strong> 171 <span class="ms-2"> 172 {{ .Timestamp }} 173 </span> 174 </div> 175 </div> 176 <div class="row"> 177 <div class="col-12"> 178 <strong class="text-muted">Labels:</strong> 179 <span class="ms-2"> 180 {{ range $index, $label := .ProfileInfo.Labels }}{{ if $index }}, {{ end }}<code class="text-info">{{ $label.Key }}</code>=<code class="text-warning">{{ $label.Value }}</code>{{ end }} 181 </span> 182 </div> 183 </div> 184 </div> 185 </div> 186 </div> 187 {{ end }} 188 </div> 189 190 <div class="card bg-dark border-secondary"> 191 <div class="card-header"> 192 <h5 class="mb-0">Call Tree</h5> 193 <small class="text-muted">Click [+]/[-] to expand/collapse nodes.</small> 194 </div> 195 <div class="card-body"> 196 {{ if .Tree }} 197 <div class="tree-view"> 198 <ul class="tree-node"> 199 {{ template "tree-node" dict "Node" .Tree "Level" 0 }} 200 </ul> 201 </div> 202 {{ else }} 203 <div class="alert alert-warning" role="alert"> 204 No call tree available for this profile. 205 </div> 206 {{ end }} 207 </div> 208 </div> 209 </div> 210 </div> 211 </main> 212 <footer class="footer mt-auto py-3 bg-dark"> 213 <div class="container"> 214 <small class="text-white-50">Status @ {{ .Now }}</small> 215 </div> 216 </footer> 217 218 <script> 219 function toggleNode(element) { 220 // We need to find the tree-children div which is a sibling of the parent tree-content div 221 const treeContent = element.parentElement; 222 const listItem = treeContent.parentElement; 223 const childrenContainer = listItem.querySelector('.tree-children'); 224 const toggle = element.querySelector('.tree-toggle'); 225 226 if (childrenContainer) { 227 if (childrenContainer.style.display === 'none') { 228 childrenContainer.style.display = 'block'; 229 toggle.textContent = '[-]'; 230 } else { 231 childrenContainer.style.display = 'none'; 232 toggle.textContent = '[+]'; 233 } 234 } 235 } 236 </script> 237 </body> 238 </html> 239 240 {{ define "tree-node" }} 241 {{ $node := .Node }} 242 {{ $level := .Level }} 243 <li> 244 {{ if gt (len $node.Children) 0 }} 245 <div class="tree-content"> 246 <div class="tree-name-section clickable" onclick="toggleNode(this)"> 247 <span class="tree-toggle">{{ if eq $level 0 }}[-]{{ else }}[+]{{ end }}</span> 248 <span class="tree-name">{{ $node.Name }}</span> 249 </div> 250 <div class="tree-value">{{ $node.FormattedValue }}</div> 251 <div class="tree-percent">{{ printf "%.2f" $node.Percent }}%</div> 252 <div class="tree-extra">{{ if $node.Location }}<span class="text-muted">{{ $node.Location }}</span>{{ end }}</div> 253 </div> 254 <ul class="tree-node tree-children" style="display: {{ if eq $level 0 }}block{{ else }}none{{ end }};"> 255 {{ range $node.Children }} 256 {{ template "tree-node" dict "Node" . "Level" (add $level 1) }} 257 {{ end }} 258 </ul> 259 {{ else }} 260 <div class="tree-content"> 261 <div class="tree-name-section"> 262 <span class="tree-toggle"></span> 263 <span class="tree-name">{{ $node.Name }}</span> 264 </div> 265 <div class="tree-value">{{ $node.FormattedValue }}</div> 266 <div class="tree-percent">{{ printf "%.2f" $node.Percent }}%</div> 267 <div class="tree-extra">{{ if $node.Location }}<span class="text-muted">{{ $node.Location }}</span>{{ end }}</div> 268 </div> 269 {{ end }} 270 </li> 271 {{ end }}