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 }}