github.com/grafana/pyroscope@v1.18.0/pkg/storegateway/gateway_blocks_http.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  
     3  package storegateway
     4  
     5  import (
     6  	_ "embed" // Used to embed html template
     7  	"fmt"
     8  	"html/template"
     9  	"net/http"
    10  	"path/filepath"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/dustin/go-humanize"
    15  	"github.com/gorilla/mux"
    16  	"github.com/prometheus/prometheus/model/labels"
    17  
    18  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    19  	"github.com/grafana/pyroscope/pkg/util"
    20  )
    21  
    22  //go:embed blocks.gohtml
    23  var blocksPageHTML string
    24  var blocksPageTemplate = template.Must(template.New("webpage").Parse(blocksPageHTML))
    25  
    26  type blocksPageContents struct {
    27  	Now             time.Time            `json:"now"`
    28  	Tenant          string               `json:"tenant,omitempty"`
    29  	RichMetas       []richMeta           `json:"metas"`
    30  	FormattedBlocks []formattedBlockData `json:"-"`
    31  	ShowDeleted     bool                 `json:"-"`
    32  	ShowSources     bool                 `json:"-"`
    33  	ShowParents     bool                 `json:"-"`
    34  	SplitCount      int                  `json:"-"`
    35  }
    36  
    37  type formattedBlockData struct {
    38  	ULID            string
    39  	ULIDTime        string
    40  	SplitID         *uint32
    41  	MinTime         string
    42  	MaxTime         string
    43  	Duration        string
    44  	DeletedTime     string
    45  	CompactionLevel int
    46  	BlockSize       string
    47  	Labels          string
    48  	Sources         []string
    49  	Parents         []string
    50  	Stats           block.BlockStats
    51  }
    52  
    53  type richMeta struct {
    54  	*block.Meta
    55  	// DeletedTime *int64  `json:"deletedTime,omitempty"`
    56  	SplitID *uint32 `json:"splitId,omitempty"`
    57  }
    58  
    59  func (s *StoreGateway) BlocksHandler(w http.ResponseWriter, req *http.Request) {
    60  	vars := mux.Vars(req)
    61  	tenantID := vars["tenant"]
    62  	if tenantID == "" {
    63  		util.WriteTextResponse(w, "Tenant ID can't be empty")
    64  		return
    65  	}
    66  
    67  	if err := req.ParseForm(); err != nil {
    68  		util.WriteTextResponse(w, fmt.Sprintf("Can't parse form: %s", err))
    69  		return
    70  	}
    71  
    72  	showDeleted := req.Form.Get("show_deleted") == "on"
    73  	showSources := req.Form.Get("show_sources") == "on"
    74  	showParents := req.Form.Get("show_parents") == "on"
    75  	var splitCount int
    76  	if sc := req.Form.Get("split_count"); sc != "" {
    77  		splitCount, _ = strconv.Atoi(sc)
    78  		if splitCount < 0 {
    79  			splitCount = 0
    80  		}
    81  	}
    82  
    83  	metasMap, err := block.ListBlocks(filepath.Join(s.gatewayCfg.BucketStoreConfig.SyncDir, tenantID), time.Time{})
    84  	if err != nil {
    85  		util.WriteTextResponse(w, fmt.Sprintf("Failed to read block metadata: %s", err))
    86  		return
    87  	}
    88  	metas := block.SortBlocks(metasMap)
    89  
    90  	formattedBlocks := make([]formattedBlockData, 0, len(metas))
    91  	richMetas := make([]richMeta, 0, len(metas))
    92  
    93  	for _, m := range metas {
    94  		// if !showDeleted && !deletedTimes[m.ULID].IsZero() {
    95  		// 	continue
    96  		// }
    97  		var parents []string
    98  		for _, pb := range m.Compaction.Parents {
    99  			parents = append(parents, pb.ULID.String())
   100  		}
   101  		var sources []string
   102  		for _, pb := range m.Compaction.Sources {
   103  			sources = append(sources, pb.String())
   104  		}
   105  		var blockSplitID *uint32
   106  		if splitCount > 0 {
   107  			bsc := block.HashBlockID(m.ULID) % uint32(splitCount)
   108  			blockSplitID = &bsc
   109  		}
   110  
   111  		var blockSize uint64
   112  		for _, f := range m.Files {
   113  			blockSize += f.SizeBytes
   114  		}
   115  
   116  		formattedBlocks = append(formattedBlocks, formattedBlockData{
   117  			ULID:            m.ULID.String(),
   118  			ULIDTime:        util.TimeFromMillis(int64(m.ULID.Time())).UTC().Format(time.RFC3339),
   119  			SplitID:         blockSplitID,
   120  			MinTime:         util.TimeFromMillis(int64(m.MinTime)).UTC().Format(time.RFC3339),
   121  			MaxTime:         util.TimeFromMillis(int64(m.MaxTime)).UTC().Format(time.RFC3339),
   122  			Duration:        util.TimeFromMillis(int64(m.MaxTime)).Sub(util.TimeFromMillis(int64(m.MinTime))).String(),
   123  			CompactionLevel: m.Compaction.Level,
   124  			BlockSize:       humanize.Bytes(blockSize),
   125  			Stats:           m.Stats,
   126  			Sources:         sources,
   127  			Parents:         parents,
   128  			Labels:          labels.FromMap(m.Labels).String(),
   129  		})
   130  
   131  		richMetas = append(richMetas, richMeta{
   132  			Meta:    m,
   133  			SplitID: blockSplitID,
   134  		})
   135  	}
   136  
   137  	util.RenderHTTPResponse(w, blocksPageContents{
   138  		Now:             time.Now(),
   139  		Tenant:          tenantID,
   140  		RichMetas:       richMetas,
   141  		FormattedBlocks: formattedBlocks,
   142  
   143  		SplitCount:  splitCount,
   144  		ShowDeleted: showDeleted,
   145  		ShowSources: showSources,
   146  		ShowParents: showParents,
   147  	}, blocksPageTemplate, req)
   148  }
   149  
   150  // func formatTimeIfNotZero(t time.Time, format string) string {
   151  // 	if t.IsZero() {
   152  // 		return ""
   153  // 	}
   154  
   155  // 	return t.Format(format)
   156  // }