{% import ( "net/url" "strconv" "strings" "github.com/VictoriaMetrics/VictoriaMetrics/lib/htmlcomponents" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils" ) %} {% stripspace %} {% func TargetsResponsePlain(tsr *targetsStatusResult, filter *requestFilter) %} {% if tsr.err != nil %} {%s= tsr.err.Error() %} {% return %} {% endif %} {% for _, jts := range tsr.jobTargetsStatuses %} job={%s= jts.jobName %}{% space %}({%d jts.upCount %}/{%d jts.targetsTotal %}{% space %}up) {% newline %} {% for _, ts := range jts.targetsStatus %} {%s= "\t" %} state={% if ts.up %}up{% else %}down{% endif %},{% space %} endpoint={%s= ts.sw.Config.ScrapeURL %},{% space %} labels={%s= ts.sw.Config.Labels.String() %},{% space %} {% if filter.showOriginalLabels %}originalLabels={%s= ts.sw.Config.OriginalLabels.String() %},{% space %}{% endif %} scrapes_total={%d ts.scrapesTotal %},{% space %} scrapes_failed={%d ts.scrapesFailed %},{% space %} last_scrape={%s ts.getDurationFromLastScrape() %},{% space %} scrape_duration={%d int(ts.scrapeDuration) %}ms,{% space %} scrape_response_size={%s ts.getSizeFromLastScrape() %},{% space %} samples_scraped={%d ts.samplesScraped %},{% space %} error={% if ts.err != nil %}{%s= ts.err.Error() %}{% endif %} {% newline %} {% endfor %} {% endfor %} {% for _, jobName := range tsr.emptyJobs %} job={%s= jobName %}{% space %}(0/0 up) {% newline %} {% endfor %} {% endfunc %} {% func TargetsResponseHTML(tsr *targetsStatusResult, filter *requestFilter) %} <!DOCTYPE html> <html lang="en"> <head> {%= htmlcomponents.CommonHeader() %} <title>Active Targets</title> </head> <body> {%= htmlcomponents.Navbar() %} <div class="container-fluid"> {% if tsr.err != nil %} {%= htmlcomponents.ErrorNotification(tsr.err) %} {% endif %} <div class="row"> <main class="col-12"> <h1>Active Targets</h1> <hr /> {%= filtersForm(filter) %} <hr /> {%= targetsTabs(tsr, filter, "scrapeTargets") %} </main> </div> </div> </body> </html> {% endfunc %} {% func ServiceDiscoveryResponse(tsr *targetsStatusResult, filter *requestFilter) %} <!DOCTYPE html> <html lang="en"> <head> {%= htmlcomponents.CommonHeader() %} <title>Discovered Targets</title> </head> <body> {%= htmlcomponents.Navbar() %} <div class="container-fluid"> {% if tsr.err != nil %} {%= htmlcomponents.ErrorNotification(tsr.err) %} {% endif %} <div class="row"> <main class="col-12"> <h1>Discovered Targets</h1> <hr /> {%= filtersForm(filter) %} <hr /> {%= targetsTabs(tsr, filter, "discoveredTargets") %} </main> </div> </div> </body> </html> {% endfunc %} {% func filtersForm(filter *requestFilter) %} <div class="row g-3 align-items-center mb-3"> <div class="col-auto"> <button id="all-btn" type="button" class="btn{% space %}{% if !filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-success{% endif %}" onclick="location.href='?{%= queryArgs(filter, map[string]string{"show_only_unhealthy": "false"}) %}'"> All </button> </div> <div class="col-auto"> <button id="unhealthy-btn" type="button" class="btn{% space %}{% if filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-danger{% endif %}" onclick="location.href='?{%= queryArgs(filter, map[string]string{"show_only_unhealthy": "true"}) %}'"> Unhealthy </button> </div> <div class="col-auto"> <button type="button" class="btn btn-primary" onclick="document.querySelectorAll('.scrape-job').forEach((el) => { el.style.display = 'none'; })"> Collapse all </button> </div> <div class="col-auto"> <button type="button" class="btn btn-secondary" onclick="document.querySelectorAll('.scrape-job').forEach((el) => { el.style.display = 'block'; })"> Expand all </button> </div> <div class="col-auto"> <button type="button" class="btn btn-success" onclick="document.getElementById('filters').style.display='block'"> Filter targets </button> </div> </div> <div id="filters" {% if filter.endpointSearch == "" && filter.labelSearch == "" %}style="display:none"{% endif %}> <form class="form-horizontal"> <div class="form-group mb-3"> <label for="endpoint_search" class="col-sm-10 control-label">Endpoint filter (<a target="_blank" href="https://github.com/google/re2/wiki/Syntax">Regexp</a> is accepted)</label> <div class="col-sm-10"> <input type="text" id="endpoint_search" name="endpoint_search" placeholder="For example, 127.0.0.1" class="form-control" value="{%s filter.endpointSearch %}"/> </div> </div> <div class="form-group mb-3"> <label for="label_search" class="col-sm-10 control-label">Labels filter (<a target="_blank" href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors">Arbitrary time series selectors</a> are accepted)</label> <div class="col-sm-10"> <input type="text" id="label_search" name="label_search" placeholder="For example, {instance=~'.+:9100'}" class="form-control" value="{%s filter.labelSearch %}"/> </div> </div> <input type="hidden" name="show_only_unhealthy" value="{%v filter.showOnlyUnhealthy %}"/> <input type="hidden" name="show_original_labels" value="{%v filter.showOriginalLabels %}"/> <button type="submit" class="btn btn-success mb-3">Submit</button> <button type="button" class="btn btn-danger mb-3" onclick="location.href='?'">Clear target filters</button> </form> </div> {% endfunc %} {% func targetsTabs(tsr *targetsStatusResult, filter *requestFilter, activeTab string) %} <ul class="nav nav-tabs" id="myTab" role="tablist"> <li class="nav-item" role="presentation"> <button class="nav-link{%if activeTab=="scrapeTargets"%}{% space %}active{%endif%}" type="button" role="tab" onclick="location.href='targets?{%= queryArgs(filter, nil) %}'"> Active targets </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link{%if activeTab=="discoveredTargets"%}{% space %}active{%endif%}" type="button" role="tab" onclick="location.href='service-discovery?{%= queryArgs(filter, nil) %}'"> Discovered targets </button> </li> </ul> <div class="tab-content"> <div class="tab-pane active" role="tabpanel"> {% switch activeTab %} {% case "scrapeTargets" %} {%= scrapeTargets(tsr) %} {% case "discoveredTargets" %} {%= discoveredTargets(tsr) %} {% endswitch %} </div> </div> {% endfunc %} {% func scrapeTargets(tsr *targetsStatusResult) %} <div class="row mt-4"> <div class="col-12"> {% for i, jts := range tsr.jobTargetsStatuses %} {%= scrapeJobTargets(i, jts, tsr.hasOriginalLabels) %} {% endfor %} {% for i, jobName := range tsr.emptyJobs %} {% code num := i + len(tsr.jobTargetsStatuses) jts := &jobTargetsStatuses{ jobName: jobName, } %} {%= scrapeJobTargets(num, jts, tsr.hasOriginalLabels) %} {% endfor %} </div> </div> {% endfunc %} {% func scrapeJobTargets(num int, jts *jobTargetsStatuses, hasOriginalLabels bool) %} <div class="row mb-4"> <div class="col-12"> <h4> <span class="me-2">{%s jts.jobName %}{% space %}({%d jts.upCount %}/{%d jts.targetsTotal %}{% space %}up)</span> {%= showHideScrapeJobButtons(num) %} </h4> <div id="scrape-job-{%d num %}" class="scrape-job table-responsive"> <table class="table table-striped table-hover table-bordered table-sm"> <thead> <tr> <th scope="col">Endpoint</th> <th scope="col">State</th> <th scope="col" title="target labels">Labels</th> {% if hasOriginalLabels %} <th scope="col" title="debug relabeling">Debug relabeling</th> {% endif %} <th scope="col" title="total scrapes">Scrapes</th> <th scope="col" title="total scrape errors">Errors</th> <th scope="col" title="the time of the last scrape">Last Scrape</th> <th scope="col" title="the duration of the last scrape">Duration</th> <th scope="col" title="the size of the last scrape">Last Scrape Size</th> <th scope="col" title="the number of metrics scraped during the last scrape">Samples</th> <th scope="col" title="error from the last scrape (if any)">Last error</th> </tr> </thead> <tbody> {% for _, ts := range jts.targetsStatus %} {% code endpoint := ts.sw.Config.ScrapeURL originalLabels := ts.sw.Config.OriginalLabels // The target is uniquely identified by a pointer to its original labels. targetID := getLabelsID(originalLabels) %} <tr {% if !ts.up %}{%space%}class="alert alert-danger" role="alert" {% endif %}> <td class="endpoint"> <a href="{%s endpoint %}" target="_blank">{%s endpoint %}</a> {% if hasOriginalLabels %} {% space %} (<a href="target_response?id={%s targetID %}" target="_blank" title="click to fetch target response on behalf of the scraper">response</a>) {% endif %} </td> <td> {% if ts.up %} <span class="badge bg-success">UP</span> {% else %} <span class="badge bg-danger">DOWN</span> {% endif %} </td> <td class="labels"> <div {% if hasOriginalLabels %} {% space %}title="click to show original labels" onclick="document.getElementById('original-labels-{%s targetID %}').style.display='block'" {% endif %} > {%= formatLabels(ts.sw.Config.Labels) %} </div> {% if hasOriginalLabels %} <div style="display:none" id="original-labels-{%s targetID %}"> {%= formatLabels(originalLabels) %} </div> {% endif %} </td> {% if hasOriginalLabels %} <td> <a href="target-relabel-debug?id={%s targetID %}" target="_blank">target</a>{% space %} <a href="metric-relabel-debug?id={%s targetID %}" target="_blank">metrics</a> </td> {% endif %} <td>{%d ts.scrapesTotal %}</td> <td>{%d ts.scrapesFailed %}</td> <td>{%s ts.getDurationFromLastScrape() %}</td> <td>{%d int(ts.scrapeDuration) %}ms</td> <td>{%s ts.getSizeFromLastScrape() %}</td> <td>{%d ts.samplesScraped %}</td> <td>{% if ts.err != nil %}{%s ts.err.Error() %}{% endif %}</td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> {% endfunc %} {% func discoveredTargets(tsr *targetsStatusResult) %} {% if !tsr.hasOriginalLabels %} <div class="alert alert-warning" role="alert"> Discovered targets are unavailable when <b>-promscrape.dropOriginalLabels</b> command-line flag is set </div> {% return %} {% endif %} {% if n := droppedTargetsMap.getTotalTargets(); n > *maxDroppedTargets %} <div class="alert alert-warning" role="alert"> Dropped targets' list below is incomplete, because the number of dropped targets exceeds <b>-promscrape.maxDroppedTargets={%d *maxDroppedTargets %}</b>.<br/> If you want to see the full list of dropped targets, then increase <b>-promscrape.maxDroppedTargets</b> command-line flag value to at least{% space %}<b>{%d n %}</b>.<br/> Note that this may increase memory usage. </div> {% endif %} {% code tljs := tsr.getTargetLabelsByJob() %} <div class="row mt-4"> <div class="col-12"> {% for i, tlj := range tljs %} {%= discoveredJobTargets(i, tlj) %} {% endfor %} </div> </div> {% endfunc %} {% func discoveredJobTargets(num int, tlj *targetLabelsByJob) %} <h4> <span class="me-2">{%s tlj.jobName %}{% space %}({%d tlj.activeTargets %}/{%d tlj.activeTargets+tlj.droppedTargets %}{% space %}active)</span> {%= showHideScrapeJobButtons(num) %} </h4> <div id="scrape-job-{%d num %}" class="scrape-job table-responsive"> <table class="table table-striped table-hover table-bordered table-sm"> <thead> <tr> <th scope="col" style="width: 5%">Status</th> <th scope="col" style="width: 60%">Discovered Labels</th> <th scope="col" style="width: 30%">Target Labels</th> <th scope="col" stile="width: 5%">Debug relabeling</a> </tr> </thead> <tbody> {% for _, t := range tlj.targets %} <tr {% if !t.up %} {% space %}role="alert"{% space %} {% if t.labels.Len() > 0 %} class="alert alert-danger" {% else %} class="alert alert-warning" {% endif %} {% endif %} > <td> {% if t.up %} <span class="badge bg-success">UP</span> {% elseif t.labels.Len() > 0 %} <span class="badge bg-danger">DOWN</span> {% else %} <span class="badge bg-warning">DROPPED ({%s string(t.dropReason) %})</span> {% if len(t.clusterMemberNums) > 0 %} <br/> <span title="The target exists at vmagent instances with the given -promscrape.cluster.memberNum values"> exists at{% space %} {% for i, memberNum := range t.clusterMemberNums %} {% if *clusterMemberURLTemplate == "" %} shard-{%d memberNum %} {% else %} <a href="{%s strings.ReplaceAll(*clusterMemberURLTemplate, "%d", strconv.Itoa(memberNum)) %}" target="_blank">shard-{%d memberNum %}</a> {% endif %} {% if i+1 < len(t.clusterMemberNums) %},{% space %}{% endif %} {% endfor %} {% endif %} {% endif %} </td> <td class="labels"> {%= formatLabels(t.originalLabels) %} </td> <td class="labels"> {%= formatLabels(t.labels) %} </td> <td> {% code targetID := getLabelsID(t.originalLabels) %} <a href="target-relabel-debug?id={%s targetID %}" target="_blank">debug</a> </td> </tr> {% endfor %} </tbody> </table> </div> {% endfunc %} {% func showHideScrapeJobButtons(num int) %} <button type="button" class="btn btn-primary btn-sm me-1" onclick="document.getElementById('scrape-job-{%d num %}').style.display='none'"> collapse </button> <button type="button" class="btn btn-secondary btn-sm me-1" onclick="document.getElementById('scrape-job-{%d num %}').style.display='block'"> expand </button> {% endfunc %} {% func queryArgs(filter *requestFilter, override map[string]string) %} {% code showOnlyUnhealthy := "false" if filter.showOnlyUnhealthy { showOnlyUnhealthy = "true" } m := map[string]string{ "show_only_unhealthy": showOnlyUnhealthy, "endpoint_search": filter.endpointSearch, "label_search": filter.labelSearch, } for k, v := range override { m[k] = v } qa := make(url.Values, len(m)) for k, v := range m { qa[k] = []string{v} } %} {%s qa.Encode() %} {% endfunc %} {% func formatLabels(labels *promutils.Labels) %} {% code labelsList := labels.GetLabels() %} { {% for i, label := range labelsList %} {%s label.Name %}={%q label.Value %} {% if i+1 < len(labelsList) %},{% space %}{% endif %} {% endfor %} } {% endfunc %} {% endstripspace %}