This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nezha-nezha-fronted/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue

2704 lines
214 KiB
Vue
Raw Normal View History

2021-08-02 19:51:53 +08:00
<template>
<div class="explore list-page">
<div class="main-list">
<div class="main-container explore-split-box">
2021-08-02 19:51:53 +08:00
<!-- 关闭按钮 -->
<div v-if="closable" class="explore-close">
<span @click="split"><i class="nz-icon nz-icon-close" :title="$t('overall.close')"></i></span>
2021-08-02 19:51:53 +08:00
</div>
<!-- 顶部工具栏 -->
<div class="top-tools" style="z-index: 1">
<el-row class="block-col-2" style="width: 300px;">
<el-col>
<el-dropdown trigger="click">
2021-11-17 18:21:56 +08:00
<span class="el-dropdown-link explore-dropdown__item">
<span><i :class="selectIcon"></i></span>
<span>{{selectValue}}</span>
<span><i class="el-icon-arrow-down el-icon--right"></i></span>
</span>
<el-dropdown-menu style="width: 118px" class="el-dropdown__width right-box-select-top right-public-box-dropdown-top" placement="bottom-end" slot="dropdown">
<el-dropdown-item
2021-08-25 13:57:04 +08:00
@click.native="selectMetricsLogs(item.label,item.icon, item.value)"
v-for="item in searchMetrics"
:key="item.value"><i class="nz-icon" :class="item.icon" style="margin-right: 5px"/>{{item.label}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
2021-08-02 19:51:53 +08:00
<div class="top-tool-right">
2022-03-24 16:37:44 +08:00
<button v-if="!closable" class="top-tool-btn top-tool-btn--text margin-r-10" style="cursor: pointer;" type="button" @click="split">
{{$t('overall.split')}}</button>
2021-08-02 19:51:53 +08:00
<pick-time id="explore" ref="pickTime" v-model="filterTime" :class="{'margin-r-10': showMetrics}" :refresh-data-func="expressionChange" @unitChange="chartUnitChange">
<!-- <template slot="added-text">{{$t('dashboard.metricPreview.runQuery')}}</template> -->
2021-08-02 19:51:53 +08:00
<template slot="added-text">{{$t('overall.query')}}</template>
</pick-time>
<el-dropdown trigger="click" :size="'medium'">
<button id="more" class="top-tool-btn" :title="$t('overall.more')">
<i class="nz-icon nz-icon-more2"></i>
</button>
<el-dropdown-menu slot="dropdown" class="right-box-select-top right-public-box-dropdown-top">
<el-dropdown-item :disabled="saveDisabled">
<div>
<i class="nz-icon nz-icon-add" />
<span v-if="showMetrics"
id="explore-save-chart"
v-has="'main_add'"
:class="{'btn-disabled-cursor-not-allowed' : saveDisabled}"
:disabled="saveDisabled"
class="top-tool-btn top-tool-btn--text"
type="button"
@click="saveChart">
{{$t('dashboard.metric.saveChart')}}
</span>
<span v-else
id="explore-save-chart-logs"
v-has="'main_add'"
:class="{'btn-disabled-cursor-not-allowed' : saveDisabled}"
:disabled="saveDisabled"
class="top-tool-btn top-tool-btn--text"
type="button"
@click="saveChartLogs">
{{$t('dashboard.metric.saveChart')}}
</span>
</div>
</el-dropdown-item>
<el-dropdown-item>
<div id="chart-export-html" @click="exportToHtml"><i class="nz-icon nz-icon-kuaizhao"></i>{{ $t('overall.snapshoot') }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
2021-08-02 19:51:53 +08:00
</div>
</div>
<div id="explore-promql-box" class="top-tools" style="padding-top: 0; flex-wrap: wrap">
<template v-if="showMetrics">
<promql-input
:from-father-data="true"
:metricOptionsParent="metricOptions"
2021-08-02 19:51:53 +08:00
v-for="index of promqlKeys.length"
:id="promqlKeys[index-1].id"
:pqid="tabIndex.toString()"
:key="promqlKeys[index-1].id"
2021-08-02 19:51:53 +08:00
:ref="'promql-'+(index-1)"
:expression-list="expressions"
:state="promqlKeys[index-1].state"
2021-08-02 19:51:53 +08:00
:index="index-1"
:plugins="['metric-selector', 'metric-input', 'add', 'remove', 'copy','enable']"
2021-08-02 19:51:53 +08:00
:styleType="1"
type="metric"
@enableExpression="enableExpression"
2021-08-02 19:51:53 +08:00
@addExpression="addExpression"
@copyExpression="copyExpression"
@removeExpression="removeExpression"
@resetExpression="resetExpression"
></promql-input>
</template>
<template v-else>
<promql-input
v-for="index of promqlKeys.length"
:id="promqlKeys[index-1].id"
:pqid="tabIndex.toString()"
:key="promqlKeys[index-1].id"
2021-08-02 19:51:53 +08:00
:ref="'promql-'+(index-1)"
:expression-list="expressions"
:state="promqlKeys[index-1].state"
2021-08-02 19:51:53 +08:00
:index="index-1"
:plugins="['metric-selector', 'metric-input', 'add', 'remove', 'copy','enable']"
2021-08-02 19:51:53 +08:00
:styleType="1"
type="log"
@enableExpression="enableExpression"
2021-08-02 19:51:53 +08:00
@addExpression="addExpression"
@copyExpression="copyExpression"
@removeExpression="removeExpression"
@resetExpression="resetExpression"
></promql-input>
</template>
</div>
<div ref="scrollWrap" style="height: auto; padding: 0 20px 4px;">
<el-collapse v-show="!showIntroduce" v-model="collapseValue" class="explore-collapse" @change="logsCollapseChange">
2021-08-02 19:51:53 +08:00
<!--metric-->
<template v-if="showMetrics">
2022-05-30 11:19:51 +08:00
<el-collapse-item name="1" :title="$t('explore.graph')" class="el-collapse-item__height">
2021-08-02 19:51:53 +08:00
<div class="chart-room">
<chart ref="exploreChart" :unit="chartUnit" :timeRange="filterTime"></chart>
2021-08-02 19:51:53 +08:00
</div>
</el-collapse-item>
2021-09-10 16:44:04 +08:00
<el-collapse-item class="el-collapse-item__height" name="2" title="Table">
<div slot="title" class="explore-table-title">
2022-05-30 11:19:51 +08:00
{{$t('dashboard.panel.chartForm.typeVal.table.label')}}
2021-12-27 17:59:57 +08:00
<i
class="nz-icon-gear nz-icon"
style="position: absolute;right: 10px;top: 8px"
@click.stop="tools.showCustomTableTitle = true"
:title="$t('overall.selectColumns')"
2021-12-27 17:59:57 +08:00
></i>
<!-- 自定义table列 -->
<transition name="el-zoom-in-top">
<element-set
@click.stop
v-if="tools.showCustomTableTitle"
:tableId="tableId"
ref="customTableTitle"
:custom-table-title="tools.customTableTitle"
:original-table-title="tableTitle"
:operation-true="true"
@close="tools.showCustomTableTitle = false"
@update="updateCustomTableTitle"
></element-set>
</transition>
</div>
<!-- 自定义table列 -->
<div class="nz-table-list explore-table">
2021-08-02 19:51:53 +08:00
<el-table ref="exploreTable"
v-my-loading="tools.loading"
class="metric-table"
:data="tableData"
border
:header-cell-class-name="({ column }) => column.property === 'gear' ? 'explore-table-gear' : ''"
tooltip-effect="light">
2021-08-02 19:51:53 +08:00
<el-table-column
v-for="(item, index) in tools.customTableTitle"
v-if="item.show"
:key="`col-${index}`"
:label="item.label"
:prop="item.prop"
:resizable="false"
min-width="110px"
show-overflow-tooltip
>
<template slot-scope="scope" :column="item">
<template v-if="item.prop === 'time'">{{timeFormate(scope.row.time)}}</template>
<span v-else-if="scope.row[item.prop]">{{scope.row[item.prop]}}</span>
<template v-else>-</template>
</template>
</el-table-column>
<template slot="empty">
2021-10-25 14:45:28 +08:00
<div v-if="!tools.loading" class="table-no-data">
<svg class="icon" aria-hidden="true">
<use xlink:href="#nz-icon-no-data-list"></use>
</svg>
2021-10-28 17:53:27 +08:00
<div class="table-no-data__title">No results found</div>
</div>
<div v-else>&nbsp;</div>
</template>
2021-08-02 19:51:53 +08:00
</el-table>
</div>
2021-12-24 16:31:31 +08:00
<pagination ref="Pagination" :page-obj="pageObj" @pageNo='pageNo'
@pageSize='pageSize'></pagination>
2021-08-02 19:51:53 +08:00
</el-collapse-item>
</template>
<!--log-->
2021-09-17 15:49:15 +08:00
<template v-else>
2022-05-30 11:19:51 +08:00
<el-collapse-item v-if="showTab.indexOf('1') > -1" name="1" :title="$t('explore.graph')" class="el-collapse-item__height">
2021-08-02 19:51:53 +08:00
<div class="chart-room">
<chart ref="logChart" :unit="chartUnit" v-my-loading="chartLoading" :timeRange="filterTime"></chart>
2021-08-02 19:51:53 +08:00
</div>
</el-collapse-item>
<el-collapse-item v-if="showTab.indexOf('2') > -1" name="2" title="Logs">
2022-08-03 11:15:36 +08:00
<log-tab ref="logDetail" :timeRange="filterTime" :log-data="logData" :explore-log-table="logTabNoData" :explore-item="true" :tab-index="tabIndex" @exportLog="exportLog" @limitChange="queryLogData" v-my-loading="chartLoading"></log-tab>
2021-08-02 19:51:53 +08:00
</el-collapse-item>
</template>
</el-collapse>
<!-- Metric -->
2021-08-02 19:51:53 +08:00
<div v-if="showMetrics" v-show="showIntroduce" class="introduce-view">
<div class="info-room title-heard">
<div class="col-md-9 logs-content">
<!-- promql -->
<h1 class="page-header" style="margin-Top:0px">PromQL: Prometheus Query Language<a class="header-anchor" href="https://prometheus.io/docs/prometheus/latest/querying/basics/" rel="noopener noreferrer" target="_blank"><i class="nz-icon nz-icon-link1" style="font-size: 16px;" :title="$t('overall.link')"></i></a></h1>
<div class="title-heard__divider"></div>
<!-- catalog 目录 -->
<div class="catalog">
<ul class="catalog-square">
<!-- Querying prometheus -->
<li>
<div @click="jumpClick('#querying-prometheus')"><span>Querying prometheus</span></div>
<ul class="catalog-disc">
<li @click="jumpClick('#querying-prometheus-examples')"><span>Examples</span></li>
<li @click="jumpClick('#expression-language-data-types')"><span>Expression language data types</span></li>
<li>
<div @click="jumpClick('#literals')"><span>Literals</span></div>
<ul>
<li @click="jumpClick('#string-literals')"><span>String literals</span></li>
<li @click="jumpClick('#float-literals')"><span>Float literals</span></li>
</ul>
</li>
<li>
<div @click="jumpClick('#time-series-selectors')"><span>Time series Selectors</span></div>
<ul>
<li @click="jumpClick('#instant-vector-selectors')"><span>Instant vector selectors</span></li>
<li @click="jumpClick('#range-vector-selectors')"><span>Range Vector Selectors</span></li>
<li @click="jumpClick('#time-durations')"><span>Time Durations</span></li>
<li @click="jumpClick('#offset-modifier')"><span>Offset modifier</span></li>
<li @click="jumpClick('#time-series-modifier')"><span>@ modifier</span></li>
</ul>
</li>
<li @click="jumpClick('#querying-prometheus-subquery')"><span>Subquery</span></li>
<li @click="jumpClick('#querying-prometheus-operators')"><span>Operators</span></li>
<li @click="jumpClick('#querying-prometheus-functions')"><span>Functions</span></li>
<li @click="jumpClick('#querying-prometheus-comments')"><span>Comments</span></li>
<li >
<div @click="jumpClick('#querying-prometheus-gotchas')"><span>Gotchas</span></div>
<ul>
<li @click="jumpClick('#gotchas-staleness')"><span>Staleness</span></li>
<li @click="jumpClick('#avoiding-slow-queries-and-overloads')"><span>Avoiding slow queries and overloads</span></li>
</ul>
</li>
</ul>
</li>
<!-- Operators -->
<li>
<div @click="jumpClick('#operators')"><span>Operators</span></div>
<ul class="catalog-disc">
<li>
<div @click="jumpClick('#binary-operators')"><span>Binary operators</span></div>
<ul>
<li @click="jumpClick('#arithmetic-binary-operators')"><span>Arithmetic binary operators</span></li>
<li @click="jumpClick('#trigonometric-binary-operators')"><span>Trigonometric binary operators</span></li>
<li @click="jumpClick('#comparison-binary-operators')"><span>Comparison binary operators</span></li>
<li @click="jumpClick('#Logical-binary-operators')"><span>Logical/set binary operators</span></li>
</ul>
</li>
<li>
<div @click="jumpClick('#vector-matching')"><span>Vector matching</span></div>
<ul>
<li @click="jumpClick('#vector-matching-keywords')"><span>Vector matching keywords</span></li>
<li @click="jumpClick('#group-modifiers')"><span>Group modifiers</span></li>
<li @click="jumpClick('#one-to-one-vector-matches')"><span>One-to-one vector matches</span></li>
<li @click="jumpClick('#many-to-one')"><span>Many-to-one and one-to-many vector matches</span></li>
</ul>
</li>
<li @click="jumpClick('#aggregation-operators')"><span>Aggregation operators</span></li>
<li @click="jumpClick('#binary-operator-precedence')"><span>Binary operator precedence</span></li>
</ul>
</li>
<!-- Functions -->
<li>
<div @click="jumpClick('#functions')"><span>Functions</span></div>
<ul class="catalog-disc">
<li @click="jumpClick('#functions-abs')"><span>abs()</span></li>
<li @click="jumpClick('#functions-absent')"><span>absent()</span></li>
<li @click="jumpClick('#functions-absent_over_time')"><span>absent_over_time()</span></li>
<li @click="jumpClick('#functions-ceil')"><span>ceil()</span></li>
<li @click="jumpClick('#functions-changes')"><span>changes()</span></li>
<li @click="jumpClick('#functions-clamp')"><span>clamp()</span></li>
<li @click="jumpClick('#functions-clamp_max')"><span>clamp_max()</span></li>
<li @click="jumpClick('#functions-clamp_min')"><span>clamp_min()</span></li>
<li @click="jumpClick('#functions-day_of_month')"><span>day_of_month()</span></li>
<li @click="jumpClick('#functions-day_of_week')"><span>day_of_week()</span></li>
<li @click="jumpClick('#functions-day_of_year')"><span>day_of_year()</span></li>
<li @click="jumpClick('#functions-days_in_month')"><span>days_in_month()</span></li>
<li @click="jumpClick('#functions-delta')"><span>delta()</span></li>
<li @click="jumpClick('#functions-deriv')"><span>deriv()</span></li>
<li @click="jumpClick('#functions-exp')"><span>exp()</span></li>
<li @click="jumpClick('#functions-floor')"><span>floor()</span></li>
<li @click="jumpClick('#functions-histogram_quantile')"><span>histogram_quantile()</span></li>
<li @click="jumpClick('#functions-holt_winters')"><span>holt_winters()</span></li>
<li @click="jumpClick('#functions-hour')"><span>hour()</span></li>
<li @click="jumpClick('#functions-idelta')"><span>idelta()</span></li>
<li @click="jumpClick('#functions-increase')"><span>increase()</span></li>
<li @click="jumpClick('#functions-irate')"><span>irate()</span></li>
<li @click="jumpClick('#functions-label_join')"><span>label_join()</span></li>
<li @click="jumpClick('#functions-label_replace')"><span>label_replace()</span></li>
<li @click="jumpClick('#functions-ln')"><span>ln()</span></li>
<li @click="jumpClick('#functions-log2')"><span>log2()</span></li>
<li @click="jumpClick('#functions-log10')"><span>log10()</span></li>
<li @click="jumpClick('#functions-minute')"><span>minute()</span></li>
<li @click="jumpClick('#functions-month')"><span>month()</span></li>
<li @click="jumpClick('#functions-predict_linear')"><span>predict_linear()</span></li>
<li @click="jumpClick('#functions-rate')"><span>rate()</span></li>
<li @click="jumpClick('#functions-resets')"><span>resets()</span></li>
<li @click="jumpClick('#functions-round')"><span>round()</span></li>
<li @click="jumpClick('#functions-scalar')"><span>scalar()</span></li>
<li @click="jumpClick('#functions-sgn')"><span>sgn()</span></li>
<li @click="jumpClick('#functions-sort')"><span>sort()</span></li>
<li @click="jumpClick('#functions-sort_desc')"><span>sort_desc()</span></li>
<li @click="jumpClick('#functions-sqrt')"><span>sqrt()</span></li>
<li @click="jumpClick('#functions-time')"><span>time()</span></li>
<li @click="jumpClick('#functions-timestamp')"><span>timestamp()</span></li>
<li @click="jumpClick('#functions-vector')"><span>vector()</span></li>
<li @click="jumpClick('#functions-year')"><span>year()</span></li>
<li @click="jumpClick('#functions-aggregation')"><span>&lt;aggregation&gt;_over_time()</span></li>
<li @click="jumpClick('#functions-trigonometric')"><span>Trigonometric Functions</span></li>
</ul>
</li>
<!-- QUERY EXAMPLES -->
<li>
<div @click="jumpClick('#query-examples')"><span>Query examples</span></div>
<ul class="catalog-disc">
<li @click="jumpClick('#simple-time-series-selection')"><span>Simple time series selection</span></li>
<li @click="jumpClick('#query-examples-subquery')"><span>Subquery</span></li>
<li @click="jumpClick('#using-functions')"><span>Using functions, operators, etc.</span></li>
</ul>
</li>
</ul>
</div>
<!-- QUERYING PROMETHEUS -->
<!-- start -->
<div class="introduce-view__content">
<h1 class="page-header-one" id="querying-prometheus">Querying prometheus</h1>
<p>Prometheus provides a functional query language called PromQL (Prometheus Query Language) that lets the user select and aggregate time series data in real time. The result of an expression can either be shown as a graph, viewed as tabular data in Prometheus's expression browser, or consumed by external systems via the HTTP API.</p>
</div>
<!-- Examples -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="querying-prometheus-examples">Examples</h1>
<p>This document is meant as a reference. For learning, it might be easier to start with a couple of <b style="color: #3C92F1" @click="jumpClick('#query-examples')" class="log-link">examples</b>.</p>
</div>
<!-- Expression language data types -->
<div >
<h1 class="page-header-two" id="expression-language-data-types">Expression language data types</h1>
<div class="introduce-view__content-label">
<p>In Prometheus's expression language, an expression or sub-expression can evaluate to one of four types:</p>
<ul>
<li><b>Instant vector</b> - a set of time series containing a single sample for each time series, all sharing the same timestamp</li>
<li><b>Range vector</b> - a set of time series containing a range of data points over time for each time series</li>
<li><b>Scalar</b> - a simple numeric floating point value</li>
<li><b>String</b> - a simple string value; currently unused</li>
</ul>
<p>Depending on the use-case (e.g. when graphing vs. displaying the output of an expression), only some of these types are legal as the result from a user-specified expression. For example, an expression that returns an instant vector is the only type that can be directly graphed.</p>
</div>
</div>
<!-- Literals -->
<h1 class="page-header-two" id="literals">Literals</h1>
<div class="introduce-view__content">
<h2 id="string-literals">String literals</h2>
<div class="introduce-view__content-label">
<p>Strings may be specified as literals in single quotes, double quotes or backticks.</p>
<p>PromQL follows the same escaping rules as Go. In single or double quotes a backslash begins an escape sequence, which may be followed by <code>a</code>, <code>b</code>, <code>f</code>, <code>n</code>, <code>r</code>, <code>t</code>, <code>v</code> or <code>\</code>. Specific characters can be provided using octal (<code>\nnn</code>) or hexadecimal (<code>\xnn</code>, <code>\unnnn</code> and <code>\Unnnnnnnn</code>).</p>
<p>No escaping is processed inside backticks. Unlike Go, Prometheus does not discard newlines inside backticks.</p>
<p>Example:</p>
<pre>"this is a string"
'these are unescaped: \n \\ \t'
`these are not unescaped: \n ' " \t`</pre>
</div>
</div>
<!-- Float literals -->
<div class="introduce-view__content">
<h2 id="float-literals">Float literals</h2>
<div class="introduce-view__content-label">
<p>Scalar float values can be written as literal integer or floating-point numbers in the format (whitespace only included for better readability):</p>
<pre>[-+]?(
[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?
| 0[xX][0-9a-fA-F]+
| [nN][aA][nN]
| [iI][nN][fF]
)</pre>
<p>Examples:</p>
<pre>23
-2.43
3.4e-9
0x8f
-Inf
NaN</pre>
</div>
</div>
<!-- Time series Selectors -->
<h1 class="page-header-two" id="time-series-selectors">Time series Selectors</h1>
<div class="introduce-view__content">
<h2 id="instant-vector-selectors">Instant vector selectors</h2>
<div class="introduce-view__content-label">
<div>
<p>Instant vector selectors allow the selection of a set of time series and a single sample value for each at a given timestamp (instant): in the simplest form, only a metric name is specified. This results in an instant vector containing elements for all time series that have this metric name.</p>
<p>This example selects all time series that have the <code>http_requests_total</code> metric name:</p>
<pre>http_requests_total</pre>
<p>It is possible to filter these time series further by appending a comma separated list of label matchers in curly braces (<code>{}</code>).</p>
<p>This example selects only those time series with the <code>http_requests_total</code> metric name that also have the <code>job</code> label set to <code>prometheus</code> and their <code>group</code> label set to <code>canary</code>:</p>
<pre>http_requests_total{job="prometheus",group="canary"}</pre>
<p>It is also possible to negatively match a label value, or to match label values against regular expressions. The following label matching operators exist:</p>
<ul>
<li><code>=</code>: Select labels that are exactly equal to the provided string.</li>
<li><code>!=</code>: Select labels that are not equal to the provided string.</li>
<li><code>=~</code>: Select labels that regex-match the provided string.</li>
<li><code>!~</code>: Select labels that do not regex-match the provided string.</li>
</ul>
<p>Regex matches are fully anchored. A match of <code>env=~"foo"</code> is treated as <code>env=~"^foo$"</code>.</p>
<p>For example, this selects all <code>http_requests_total</code> time series for <code>staging</code>, <code>testing</code>, and <code>development</code> environments and HTTP methods other than <code>GET</code>.</p>
<pre>http_requests_total{environment=~"staging|testing|development",method!="GET"}</pre>
</div>
<p>Label matchers that match empty label values also select all time series that do not have the specific label set at all. It is possible to have multiple matchers for the same label name.</p>
<p>Vector selectors must either specify a name or at least one label matcher that does not match the empty string. The following expression is illegal:</p>
<pre>{job=~".*"} # Bad!</pre>
<p>In contrast, these expressions are valid as they both have a selector that does not match empty label values.</p>
<pre>{job=~".+"} # Good!
{job=~".*",method="get"} # Good!</pre>
<p>Label matchers can also be applied to metric names by matching against the internal <code>__name__</code> label. For example, the expression <code>http_requests_total</code> is equivalent to <code>{__name__="http_requests_total"}</code>. Matchers other than <code>=</code> (<code>!=</code>, <code>=~</code>, <code>!~</code>) may also be used. The following expression selects all metrics that have a name starting with <code>job:</code>:</p>
<pre>{__name__=~"job:.*"}</pre>
<p>The metric name must not be one of the keywords <code>bool</code>, <code>on</code>, <code>ignoring</code>, <code>group_left</code> and <code>group_right</code>. The following expression is illegal:</p>
<pre>on{} # Bad!</pre>
<p>A workaround for this restriction is to use the <code>__name__</code> label:</p>
<pre>{__name__="on"} # Good!</pre>
<p>All regular expressions in Prometheus use RE2 syntax.</p>
</div>
</div>
<!-- Range Vector Selectors -->
<div class="introduce-view__content">
<h2 id="range-vector-selectors">Range Vector Selectors</h2>
<div class="introduce-view__content-label">
<p>Range vector literals work like instant vector literals, except that they select a range of samples back from the current instant. Syntactically, a <b style="color: #3C92F1" @click="jumpClick('#time-durations')" class="log-link">time duration</b> is appended in square brackets (<code>[]</code>) at the end of a vector selector to specify how far back in time values should be fetched for each resulting range vector element.</p>
<p>In this example, we select all the values we have recorded within the last 5 minutes for all time series that have the metric name <code>http_requests_total</code> and a <code>job</code> label set to <code>prometheus</code>:</p>
<pre>http_requests_total{job="prometheus"}[5m]</pre>
</div>
</div>
<!-- Time Durations -->
<div class="introduce-view__content">
<h2 id="time-durations">Time Durations</h2>
<div class="introduce-view__content-label">
<p>Time durations are specified as a number, followed immediately by one of the following units:</p>
<ul>
<li><code>ms</code> - milliseconds</li>
<li><code>s</code> - seconds</li>
<li><code>m</code> - minutes</li>
<li><code>h</code> - hours</li>
<li><code>d</code> - days - assuming a day has always 24h</li>
<li><code>w</code> - weeks - assuming a week has always 7d</li>
<li><code>y</code> - years - assuming a year has always 365d</li>
</ul>
<p>Time durations can be combined, by concatenation. Units must be ordered from the longest to the shortest. A given unit must only appear once in a time duration.</p>
<p>Here are some examples of valid time durations:</p>
<pre>5h
1h30m
5m
10s</pre>
</div>
</div>
<!-- Offset modifier -->
<div class="introduce-view__content">
<h2 id="offset-modifier">Offset modifier</h2>
<div class="introduce-view__content-label">
<p>The <code>offset</code> modifier allows changing the time offset for individual instant and range vectors in a query.</p>
<p>For example, the following expression returns the value of <code>http_requests_total</code> 5 minutes in the past relative to the current query evaluation time:</p>
<pre>http_requests_total offset 5m</pre>
<p>Note that the <code>offset</code> modifier always needs to follow the selector immediately, i.e. the following would be correct:</p>
<pre>sum(http_requests_total{method="GET"} offset 5m) // GOOD.</pre>
<p>While the following would be <i>incorrect</i>:</p>
<pre>sum(http_requests_total{method="GET"}) offset 5m // INVALID.</pre>
<p>The same works for range vectors. This returns the 5-minute rate that <code>http_requests_total</code> had a week ago:</p>
<pre>rate(http_requests_total[5m] offset 1w)</pre>
<p>For comparisons with temporal shifts forward in time, a negative offset can be specified:</p>
<pre>rate(http_requests_total[5m] offset -1w)</pre>
<p>Note that this allows a query to look ahead of its evaluation time.</p>
</div>
</div>
<!-- @ modifier -->
<div class="introduce-view__content">
<h2 id="time-series-modifier">@ modifier</h2>
<div class="introduce-view__content-label">
<p>The <code>@</code> modifier allows changing the evaluation time for individual instant and range vectors in a query. The time supplied to the <code>@</code> modifier is a unix timestamp and described with a float literal.</p>
<p>For example, the following expression returns the value of <code>http_requests_total</code> at <code>2021-01-04T07:40:00+00:00</code>:</p>
<pre>http_requests_total @ 1609746000</pre>
<p>Note that the <code>@</code> modifier always needs to follow the selector immediately, i.e. the following would be correct:</p>
<pre>sum(http_requests_total{method="GET"} @ 1609746000) // GOOD.</pre>
<p>While the following would be <i>incorrect</i>:</p>
<pre>sum(http_requests_total{method="GET"}) @ 1609746000 // INVALID.</pre>
<p>The same works for range vectors. This returns the 5-minute rate that <code>http_requests_total</code> had at <code>2021-01-04T07:40:00+00:00</code>:</p>
<pre>rate(http_requests_total[5m] @ 1609746000)</pre>
<p>The <code>@</code> modifier supports all representation of float literals described above within the limits of <code>int64</code>. It can also be used along with the <code>offset</code> modifier where the offset is applied relative to the <code>@</code> modifier time irrespective of which modifier is written first. These 2 queries will produce the same result.</p>
<pre># offset after @
http_requests_total @ 1609746000 offset 5m
# offset before @
http_requests_total offset 5m @ 1609746000</pre>
<p>Additionally, <code>start()</code> and <code>end()</code> can also be used as values for the <code>@</code> modifier as special values.</p>
<p>For a range query, they resolve to the start and end of the range query respectively and remain the same for all steps.</p>
<p>For an instant query, <code>start()</code> and <code>end()</code> both resolve to the evaluation time.</p>
<pre>http_requests_total @ start()
rate(http_requests_total[5m] @ end())</pre>
<p>Note that the <code>@</code> modifier allows a query to look ahead of its evaluation time.</p>
</div>
</div>
<!-- Subquery -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="querying-prometheus-subquery">Subquery</h1>
<div class="introduce-view__content-label">
<p>Subquery allows you to run an instant query for a given range and resolution. The result of a subquery is a range vector.</p>
<p>Syntax: <code>&lt;instant_query&gt; '[' &lt;range&gt; ':' [&lt;resolution&gt;] ']' [ @ &lt;float_literal&gt; ] [ offset &lt;duration&gt; ]</code></p>
<ul>
<li><code>&lt;resolution&gt;</code> is optional. Default is the global evaluation interval.</li>
</ul>
</div>
</div>
<!-- Operators -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="querying-prometheus-operators">Operators</h1>
<div class="introduce-view__content-label">
<p>Prometheus supports many binary and aggregation operators. These are described in detail in the <b style="color: #3C92F1" @click="jumpClick('#operators')" class="log-link">expression language operators</b> page.</p>
</div>
</div>
<!-- Functions -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="querying-prometheus-functions">Functions</h1>
<div class="introduce-view__content-label">
<p>Prometheus supports several functions to operate on data. These are described in detail in the <b style="color: #3C92F1" @click="jumpClick('#functions')" class="log-link">expression language functions</b> page.</p>
</div>
</div>
<!-- Comments -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="querying-prometheus-comments">Comments</h1>
<div class="introduce-view__content-label">
<p>PromQL supports line comments that start with <code>#</code>. Example:</p>
<pre> # This is a comment</pre>
</div>
</div>
<!-- Gotchas -->
<h1 class="page-header-two" id="querying-prometheus-gotchas">Gotchas</h1>
<div class="introduce-view__content">
<h2 id="gotchas-staleness">Staleness</h2>
<div class="introduce-view__content-label">
<p>When queries are run, timestamps at which to sample data are selected independently of the actual present time series data. This is mainly to support cases like aggregation (<code>sum</code>, <code>avg</code>, and so on), where multiple aggregated time series do not exactly align in time. Because of their independence, Prometheus needs to assign a value at those timestamps for each relevant time series. It does so by simply taking the newest sample before this timestamp.</p>
<p>If a target scrape or rule evaluation no longer returns a sample for a time series that was previously present, that time series will be marked as stale. If a target is removed, its previously returned time series will be marked as stale soon afterwards.</p>
<p>If a query is evaluated at a sampling timestamp after a time series is marked stale, then no value is returned for that time series. If new samples are subsequently ingested for that time series, they will be returned as normal.</p>
<p>If no sample is found (by default) 5 minutes before a sampling timestamp, no value is returned for that time series at this point in time. This effectively means that time series "disappear" from graphs at times where their latest collected sample is older than 5 minutes or after they are marked stale.</p>
<p>Staleness will not be marked for time series that have timestamps included in their scrapes. Only the 5 minute threshold will be applied in that case.</p>
</div>
</div>
<!-- Avoiding slow queries and overloads -->
<div class="introduce-view__content">
<h2 id="avoiding-slow-queries-and-overloads">Avoiding slow queries and overloads</h2>
<div class="introduce-view__content-label">
<p>If a query needs to operate on a very large amount of data, graphing it might time out or overload the server or browser. Thus, when constructing queries over unknown data, always start building the query in the tabular view of Prometheus's expression browser until the result set seems reasonable (hundreds, not thousands, of time series at most). Only when you have filtered or aggregated your data sufficiently, switch to graph mode. If the expression still takes too long to graph ad-hoc, pre-record it via a recording rule.</p>
<p>This is especially relevant for Prometheus's query language, where a bare metric name selector like <code>api_http_requests_total</code> could expand to thousands of time series with different labels. Also keep in mind that expressions which aggregate over many time series will generate load on the server even if the output is only a small number of time series. This is similar to how it would be slow to sum all values of a column in a relational database, even if the output value is only a single number.</p>
</div>
</div>
<!-- OPERATORS -->
<h1 class="page-header-one" id="operators">Operators</h1>
<div class="introduce-view__content">
<h1 class="page-header-two" id="binary-operators">Binary operators</h1>
<p>Prometheus's query language supports basic logical and arithmetic operators. For operations between two instant vectors, the <b style="color: #3C92F1" @click="jumpClick('#vector-matching')" class="log-link">matching behavior</b> can be modified.</p>
</div>
<!-- Arithmetic binary operators -->
<div class="introduce-view__content">
<h2 id="arithmetic-binary-operators">Arithmetic binary operators</h2>
<div class="introduce-view__content-label">
<p>The following binary arithmetic operators exist in Prometheus:</p>
<ul>
<li><code>+</code> (addition)</li>
<li><code>-</code> (subtraction)</li>
<li><code>*</code> (multiplication)</li>
<li><code>/</code> (division)</li>
<li><code>%</code> (modulo)</li>
<li><code>^</code> (power/exponentiation)</li>
</ul>
<p>Binary arithmetic operators are defined between scalar/scalar, vector/scalar, and vector/vector value pairs.</p>
<p><b>Between two scalars</b>, the behavior is obvious: they evaluate to another scalar that is the result of the operator applied to both scalar operands.</p>
<p><b>Between an instant vector and a scalar</b>, the operator is applied to the value of every data sample in the vector. E.g. if a time series instant vector is multiplied by 2, the result is another vector in which every sample value of the original vector is multiplied by 2. The metric name is dropped.</p>
<p><b>Between two instant vectors</b>, a binary arithmetic operator is applied to each entry in the left-hand side vector and its matching element in the right-hand vector. The result is propagated into the result vector with the grouping labels becoming the output label set. The metric name is dropped. Entries for which no matching entry in the right-hand vector can be found are not part of the result.</p>
</div>
</div>
<!-- Trigonometric binary operators -->
<div class="introduce-view__content">
<h2 id="trigonometric-binary-operators">Trigonometric binary operators</h2>
<div class="introduce-view__content-label">
<p>The following trigonometric binary operators, which work in radians, exist in Prometheus:</p>
<ul>
<li><code>atan2</code> (based on https://pkg.go.dev/math#Atan2)</li>
</ul>
<p>Trigonometric operators allow trigonometric functions to be executed on two vectors using vector matching, which isn't available with normal functions. They act in the same manner as arithmetic operators.</p>
</div>
</div>
<!-- Comparison binary operators -->
<div class="introduce-view__content">
<h2 id="comparison-binary-operators">Comparison binary operators</h2>
<div class="introduce-view__content-label">
<p>The following binary comparison operators exist in Prometheus:</p>
<ul>
<li><code>==</code> (equal)</li>
<li><code>!=</code> (not-equal)</li>
<li><code>&gt;</code> (greater-than)</li>
<li><code>&lt;</code> (less-than)</li>
<li><code>&gt;=</code> (greater-or-equal)</li>
<li><code>&lt;=</code> (less-or-equal)</li>
</ul>
<p>Comparison operators are defined between scalar/scalar, vector/scalar, and vector/vector value pairs. By default they filter. Their behavior can be modified by providing <code>bool</code> after the operator, which will return <code>0</code> or <code>1</code> for the value rather than filtering.</p>
<p><b>Between two scalars</b>, the <code>bool</code> modifier must be provided and these operators result in another scalar that is either <code>0</code> (<code>false</code>) or <code>1</code> (<code>true</code>), depending on the comparison result.</p>
<p><b>Between an instant vector and a scalar</b>, these operators are applied to the value of every data sample in the vector, and vector elements between which the comparison result is <code>false</code> get dropped from the result vector. If the <code>bool</code> modifier is provided, vector elements that would be dropped instead have the value <code>0</code> and vector elements that would be kept have the value <code>1</code>. The metric name is dropped if the <code>bool</code> modifier is provided.</p>
<p><b>Between two instant vectors</b>, these operators behave as a filter by default, applied to matching entries. Vector elements for which the expression is not true or which do not find a match on the other side of the expression get dropped from the result, while the others are propagated into a result vector with the grouping labels becoming the output label set. If the <code>bool</code> modifier is provided, vector elements that would have been dropped instead have the value <code>0</code> and vector elements that would be kept have the value <code>1</code>, with the grouping labels again becoming the output label set. The metric name is dropped if the <code>bool</code> modifier is provided.</p>
</div>
</div>
<!-- Logical/set binary operators -->
<div class="introduce-view__content">
<h2 id="Logical-binary-operators">Logical/set binary operators</h2>
<div class="introduce-view__content-label">
<p>These logical/set binary operators are only defined between instant vectors:</p>
<ul>
<li><code>and</code> (intersection)</li>
<li><code>or</code> (union)</li>
<li><code>unless</code> (complement)</li>
</ul>
<p><code>vector1 and vector2</code> results in a vector consisting of the elements of <code>vector1</code> for which there are elements in <code>vector2</code> with exactly matching label sets. Other elements are dropped. The metric name and values are carried over from the left-hand side vector.</p>
<p><code>vector1 or vector2</code> results in a vector that contains all original elements (label sets + values) of <code>vector1</code> and additionally all elements of <code>vector2</code> which do not have matching label sets in <code>vector1</code>.</p>
<p><code>vector1 unless vector2</code> results in a vector consisting of the elements of <code>vector1</code> for which there are no elements in <code>vector2</code> with exactly matching label sets. All matching elements in both vectors are dropped.</p>
</div>
</div>
<!-- Vector matching -->
<h1 class="page-header-two" id="vector-matching">Vector matching</h1>
<div class="introduce-view__content">
<div class="introduce-view__content-label">
<p>Operations between vectors attempt to find a matching element in the right-hand side vector for each entry in the left-hand side. There are two basic types of matching behavior: One-to-one and many-to-one/one-to-many.</p>
</div>
</div>
<!-- Vector matching keywords -->
<div class="introduce-view__content">
<h2 id="vector-matching-keywords">Vector matching keywords</h2>
<div class="introduce-view__content-label">
<p>These vector matching keywords allow for matching between series with different label sets providing:</p>
<ul>
<li><code>on</code></li>
<li><code>ignoring</code></li>
</ul>
<p>Label lists provided to matching keywords will determine how vectors are combined. Examples can be found in <b style="color: #3C92F1" @click="jumpClick('#one-to-one-vector-matches')" class="log-link">One-to-one vector matches</b> and in <b style="color: #3C92F1" @click="jumpClick('#many-to-one')" class="log-link">Many-to-one and one-to-many vector matches</b></p>
</div>
</div>
<!-- Group modifiers -->
<div class="introduce-view__content">
<h2 id="group-modifiers">Group modifiers</h2>
<div class="introduce-view__content-label">
<p>These group modifiers enable many-to-one/one-to-many vector matching:</p>
<ul>
<li><code>group_left</code></li>
<li><code>group_right</code></li>
</ul>
<p>Label lists can be provided to the group modifier which contain labels from the "one"-side to be included in the result metrics.</p>
<p style="font-style:italic;">Many-to-one and one-to-many matching are advanced use cases that should be carefully considered. Often a proper use of <code>ignoring(&lt;labels&gt;)</code> provides the desired outcome.</p>
<p style="font-style:italic;">Grouping modifiers can only be used for <b style="color: #3C92F1" @click="jumpClick('#comparison-binary-operators')" class="log-link">comparison</b> and <b style="color: #3C92F1" @click="jumpClick('#arithmetic-binary-operators')" class="log-link">arithmetic</b>. Operations as <code>and</code>, <code>unless</code> and <code>or</code> operations match with all possible entries in the right vector by default.</p>
</div>
</div>
<!-- One-to-one vector matches -->
<div class="introduce-view__content">
<h2 id="one-to-one-vector-matches">One-to-one vector matches</h2>
<div class="introduce-view__content-label">
<p><b>One-to-one</b> finds a unique pair of entries from each side of the operation. In the default case, that is an operation following the format <code>vector1 &lt;operator&gt; vector2</code>. Two entries match if they have the exact same set of labels and corresponding values. The <code>ignoring</code> keyword allows ignoring certain labels when matching, while the <code>on</code> keyword allows reducing the set of considered labels to a provided list:</p>
<pre>&lt;vector expr&gt; &lt;bin-op&gt; ignoring(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) &lt;vector expr&gt;</pre>
<p>Example input:</p>
<pre>method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
2021-08-02 19:51:53 +08:00
method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120</pre>
<p>Example query:</p>
<pre>method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m</pre>
<p>This returns a result vector containing the fraction of HTTP requests with status code of 500 for each method, as measured over the last 5 minutes. Without <code>ignoring(code)</code> there would have been no match as the metrics do not share the same set of labels. The entries with methods <code>put</code> and <code>del</code> have no match and will not show up in the result:</p>
<pre>{method="get"} 0.04 // 24 / 600
{method="post"} 0.05 // 6 / 120</pre>
</div>
</div>
<!-- Many-to-one and one-to-many vector matches -->
<div class="introduce-view__content">
<h2 id="many-to-one">Many-to-one and one-to-many vector matches</h2>
<div class="introduce-view__content-label">
<p><b>Many-to-one</b> and <b>one-to-many</b> matchings refer to the case where each vector element on the "one"-side can match with multiple elements on the "many"-side. This has to be explicitly requested using the <code>group_left</code> or <code>group_right</code> modifiers, where left/right determines which vector has the higher cardinality.</p>
<pre>&lt;vector expr> &lt;bin-op&gt; ignoring(&lt;label list&gt;) group_left(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; ignoring(&lt;label list&gt;) group_right(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) group_left(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) group_right(&lt;label list&gt;) &lt;vector expr&gt;</pre>
<p>The label list provided with the group modifier contains additional labels from the "one"-side to be included in the result metrics. For <code>on</code> a label can only appear in one of the lists. Every time series of the result vector must be uniquely identifiable.</p>
<p>Example query:</p>
<pre>method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m</pre>
<p>In this case the left vector contains more than one entry per <code>method</code> label value. Thus, we indicate this using <code>group_left</code>. The elements from the right side are now matched with multiple elements with the same <code>method</code> label on the left:</p>
<pre>{method="get", code="500"} 0.04 // 24 / 600
{method="get", code="404"} 0.05 // 30 / 600
{method="post", code="500"} 0.05 // 6 / 120
{method="post", code="404"} 0.175 // 21 / 120</pre>
</div>
</div>
<!-- Aggregation operators -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="aggregation-operators">Aggregation operators</h1>
<div class="introduce-view__content-label">
<p>Prometheus supports the following built-in aggregation operators that can be used to aggregate the elements of a single instant vector, resulting in a new vector of fewer elements with aggregated values:</p>
<ul>
<li><code>sum</code> (calculate sum over dimensions)</li>
<li><code>min</code> (select minimum over dimensions)</li>
<li><code>max</code> (select maximum over dimensions)</li>
<li><code>avg</code> (calculate the average over dimensions)</li>
<li><code>group</code> (all values in the resulting vector are 1)</li>
<li><code>stddev</code> (calculate population standard deviation over dimensions)</li>
<li><code>stdvar</code> (calculate population standard variance over dimensions)</li>
<li><code>count</code> (count number of elements in the vector)</li>
<li><code>count_values</code> (count number of elements with the same value)</li>
<li><code>bottomk</code> (smallest k elements by sample value)</li>
<li><code>topk</code> (largest k elements by sample value)</li>
<li><code>quantile</code> (calculate φ-quantile (0 φ 1) over dimensions)</li>
</ul>
<p>These operators can either be used to aggregate over <b>all</b> label dimensions or preserve distinct dimensions by including a <code>without</code> or <code>by</code> clause. These clauses may be used before or after the expression.</p>
<pre>&lt;aggr-op&gt; [without|by (&lt;label list&gt;)] ([parameter,] &lt;vector expression&gt;)</pre>
<p>or</p>
<pre>&lt;aggr-op&gt;([parameter,] &lt;vector expression&gt;) [without|by (&lt;label list&gt;)]</pre>
<p><code>label list</code> is a list of unquoted labels that may include a trailing comma, i.e. both <code>(label1, label2)</code> and <code>(label1, label2,)</code> are valid syntax.</p>
<p><code>without</code> removes the listed labels from the result vector, while all other labels are preserved in the output. <code>by</code> does the opposite and drops labels that are not listed in the <code>by</code> clause, even if their label values are identical between all elements of the vector.</p>
<p><code>parameter</code> is only required for <code>count_values</code>, <code>quantile</code>, <code>topk</code> and <code>bottomk</code>.</p>
<p><code>count_values</code> outputs one time series per unique sample value. Each series has an additional label. The name of that label is given by the aggregation parameter, and the label value is the unique sample value. The value of each time series is the number of times that sample value was present.</p>
<p><code>topk</code> and <code>bottomk</code> are different from other aggregators in that a subset of the input samples, including the original labels, are returned in the result vector. <code>by</code> and <code>without</code> are only used to bucket the input vector.</p>
<p><code>quantile</code> calculates the φ-quantile, the value that ranks at number φ*N among the N metric values of the dimensions aggregated over. φ is provided as the aggregation parameter. For example, <code>quantile(0.5, ...)</code> calculates the median, <code>quantile(0.95, ...)</code> the 95th percentile. For φ = <code>NaN</code>, <code>NaN</code> is returned. For φ &lt; 0, -Inf is returned. For φ > 1, <code>+Inf</code> is returned.</p>
<p>Example:</p>
<p>If the metric <code>http_requests_total</code> had time series that fan out by <code>application</code>, <code>instance</code>, and <code>group</code> labels, we could calculate the total number of seen HTTP requests per application and group over all instances via:</p>
<pre>sum without (instance) (http_requests_total)</pre>
<p>Which is equivalent to:</p>
<pre> sum by (application, group) (http_requests_total)</pre>
<p>If we are just interested in the total of HTTP requests we have seen in <b>all</b> applications, we could simply write:</p>
<pre>sum(http_requests_total)</pre>
<p>To count the number of binaries running each build version we could write:</p>
<pre>count_values("version", build_version)</pre>
<p>To get the 5 largest HTTP requests counts across all instances we could write:</p>
<pre>topk(5, http_requests_total)</pre>
</div>
</div>
<!-- Binary operator precedence -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="binary-operator-precedence">Binary operator precedence</h1>
<div class="introduce-view__content-label binary-operator-precedence">
<p>The following list shows the precedence of binary operators in Prometheus, from highest to lowest.</p>
<ul>
<li><code>^</code></li>
<li><code>*</code>, <code>/</code>, <code>%</code>, <code>atan2</code></li>
<li><code>+</code>, <code>-</code></li>
<li><code>==</code>, <code>!=</code>, <code>&lt;=</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&gt;</code></li>
<li><code>and</code>, <code>unless</code></li>
<li><code>or</code></li>
</ul>
<p>Operators on the same precedence level are left-associative. For example, <code>2 * 3 % 2</code> is equivalent to <code>(2 * 3) % 2</code>. However <code>^</code> is right associative, so <code>2 ^ 3 ^ 2</code> is equivalent to <code>2 ^ (3 ^ 2)</code>.</p>
</div>
</div>
<!-- FUNCTIONS -->
<h1 class="page-header-one" id="functions">Functions</h1>
<div class="introduce-view__content">
<div class="introduce-view__content-label">
<p>Some functions have default arguments, e.g. <code>year(v=vector(time()) instant-vector)</code>. This means that there is one argument v which is an instant vector, which if not provided it will default to the value of the expression <code>vector(time())</code>.</p>
</div>
</div>
<!-- abs() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-abs">abs()</h1>
<div class="introduce-view__content-label">
<p><code>abs(v instant-vector)</code> returns the input vector with all sample values converted to their absolute value.</p>
</div>
</div>
<!-- absent() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-absent">absent()</h1>
<div class="introduce-view__content-label">
<p><code>absent(v instant-vector)</code> returns an empty vector if the vector passed to it has any elements and a 1-element vector with the value 1 if the vector passed to it has no elements.</p>
<p>This is useful for alerting on when no time series exist for a given metric name and label combination.</p>
<pre>absent(nonexistent{job="myjob"})
# => {job="myjob"}
2021-08-02 19:51:53 +08:00
absent(nonexistent{job="myjob",instance=~".*"})
# => {job="myjob"}
2021-08-02 19:51:53 +08:00
absent(sum(nonexistent{job="myjob"}))
# => {}</pre>
<p>In the first two examples, <code>absent()</code> tries to be smart about deriving labels of the 1-element output vector from the input vector.</p>
</div>
</div>
<!-- absent_over_time() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-absent_over_time">absent_over_time()</h1>
<div class="introduce-view__content-label">
<p><code>absent_over_time(v range-vector)</code> returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements.</p>
<p>This is useful for alerting on when no time series exist for a given metric name and label combination for a certain amount of time.</p>
<pre>absent_over_time(nonexistent{job="myjob"}[1h])
# => {job="myjob"}
2021-08-02 19:51:53 +08:00
absent_over_time(nonexistent{job="myjob",instance=~".*"}[1h])
# => {job="myjob"}
2021-08-02 19:51:53 +08:00
absent_over_time(sum(nonexistent{job="myjob"})[1h:])
# => {}</pre>
<p>In the first two examples, <code>absent_over_time()</code> tries to be smart about deriving labels of the 1-element output vector from the input vector.</p>
</div>
</div>
<!-- ceil() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-ceil">ceil()</h1>
<div class="introduce-view__content-label">
<p><code>ceil(v instant-vector)</code> rounds the sample values of all elements in <code>v</code> up to the nearest integer.</p>
</div>
</div>
<!-- changes() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-changes">changes()</h1>
<div class="introduce-view__content-label">
<p>For each input time series, <code>changes(v range-vector)</code> returns the number of times its value has changed within the provided time range as an instant vector.</p>
</div>
</div>
<!-- clamp() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-clamp">clamp()</h1>
<div class="introduce-view__content-label">
<p><code>clamp(v instant-vector, min scalar, max scalar)</code> clamps the sample values of all elements in <code>v</code> to have a lower limit of <code>min</code> and an upper limit of <code>max</code>.</p>
<p>Special cases: - Return an empty vector if <code>min > max</code> - Return <code>NaN</code> if <code>min</code> or <code>max</code> is <code>NaN</code></p>
</div>
</div>
<!-- clamp_max() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-clamp_max">clamp_max()</h1>
<div class="introduce-view__content-label">
<p><code>clamp_max(v instant-vector, max scalar)</code> clamps the sample values of all elements in <code>v</code> to have an upper limit of <code>max</code>.</p>
</div>
</div>
<!-- clamp_min() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-clamp_min">clamp_min()</h1>
<div class="introduce-view__content-label">
<p><code>clamp_min(v instant-vector, min scalar)</code> clamps the sample values of all elements in <code>v</code> to have a lower limit of <code>min</code>.</p>
</div>
</div>
<!-- day_of_month() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-day_of_month">day_of_month()</h1>
<div class="introduce-view__content-label">
<p><code>day_of_month(v=vector(time()) instant-vector)</code> returns the day of the month for each of the given times in UTC. Returned values are from 1 to 31.</p>
</div>
</div>
<!-- day_of_week() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-day_of_week">day_of_week()</h1>
<div class="introduce-view__content-label">
<p><code>day_of_week(v=vector(time()) instant-vector)</code> returns the day of the week for each of the given times in UTC. Returned values are from 0 to 6, where 0 means Sunday etc.</p>
</div>
</div>
<!-- day_of_year() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-day_of_year">day_of_year()</h1>
<div class="introduce-view__content-label">
<p><code>day_of_year(v=vector(time()) instant-vector)</code> returns the day of the year for each of the given times in UTC. Returned values are from 1 to 365 for non-leap years, and 1 to 366 in leap years.</p>
</div>
</div>
<!-- days_in_month() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-days_in_month">days_in_month()</h1>
<div class="introduce-view__content-label">
<p><code>days_in_month(v=vector(time()) instant-vector)</code> returns number of days in the month for each of the given times in UTC. Returned values are from 28 to 31.</p>
</div>
</div>
<!-- delta() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-delta">delta()</h1>
<div class="introduce-view__content-label">
<p><code>delta(v range-vector)</code> calculates the difference between the first and last value of each time series element in a range vector <code>v</code>, returning an instant vector with the given deltas and equivalent labels. The delta is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if the sample values are all integers.</p>
<p>The following example expression returns the difference in CPU temperature between now and 2 hours ago:</p>
<pre>delta(cpu_temp_celsius{host="zeus"}[2h])</pre>
<p><code>delta</code> should only be used with gauges.</p>
</div>
</div>
<!-- deriv() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-deriv">deriv()</h1>
<div class="introduce-view__content-label">
<p><code>deriv(v range-vector)</code> calculates the per-second derivative of the time series in a range vector <code>v</code>, using simple linear regression. The range vector must have at least two samples in order to perform the calculation. When <code>+Inf</code> or <code>-Inf</code> are found in the range vector, the slope and offset value calculated will be <code>NaN</code>.</p>
<p><code>deriv</code> should only be used with gauges.</p>
</div>
</div>
<!-- exp() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-exp">exp()</h1>
<div class="introduce-view__content-label">
<p><code>exp(v instant-vector)</code> calculates the exponential function for all elements in <code>v</code>. Special cases are:</p>
<ul>
<li><code>Exp(+Inf) = +Inf</code></li>
<li><code>Exp(NaN) = NaN</code></li>
</ul>
</div>
</div>
<!-- floor() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-floor">floor()</h1>
<div class="introduce-view__content-label">
<p><code>floor(v instant-vector)</code> rounds the sample values of all elements in <code>v</code> down to the nearest integer.</p>
</div>
</div>
<!-- histogram_quantile() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-histogram_quantile">histogram_quantile()</h1>
<div class="introduce-view__content-label">
<p><code>histogram_quantile(φ scalar, b instant-vector)</code> calculates the φ-quantile (0 φ 1) from the buckets <code>b</code> of a histogram. (See histograms and summaries for a detailed explanation of φ-quantiles and the usage of the histogram metric type in general.) The samples in b are the counts of observations in each bucket. Each sample must have a label <code>le</code> where the label value denotes the inclusive upper bound of the bucket. (Samples without such a label are silently ignored.) The histogram metric type automatically provides time series with the <code>_bucket</code> suffix and the appropriate labels.</p>
<p>Use the <code>rate()</code> function to specify the time window for the quantile calculation.</p>
<p>Example: A histogram metric is called <code>http_request_duration_seconds</code>. To calculate the 90th percentile of request durations over the last 10m, use the following expression:</p>
<pre>histogram_quantile(0.9, rate(http_request_duration_seconds_bucket[10m]))</pre>
<p>The quantile is calculated for each label combination in <code>http_request_duration_seconds</code>. To aggregate, use the <code>sum()</code> aggregator around the <code>rate()</code> function. Since the <code>le</code> label is required by <code>histogram_quantile()</code>, it has to be included in the <code>by</code> clause. The following expression aggregates the 90th percentile by <code>job</code>:</p>
<pre>histogram_quantile(0.9, sum by (job, le) (rate(http_request_duration_seconds_bucket[10m])))</pre>
<p>To aggregate everything, specify only the <code>le</code> label:</p>
<pre>histogram_quantile(0.9, sum by (le) (rate(http_request_duration_seconds_bucket[10m])))</pre>
<p>The <code>histogram_quantile()</code> function interpolates quantile values by assuming a linear distribution within a bucket. The highest bucket must have an upper bound of <code>+Inf</code>. (Otherwise, <code>NaN</code> is returned.) If a quantile is located in the highest bucket, the upper bound of the second highest bucket is returned. A lower limit of the lowest bucket is assumed to be 0 if the upper bound of that bucket is greater than 0. In that case, the usual linear interpolation is applied within that bucket. Otherwise, the upper bound of the lowest bucket is returned for quantiles located in the lowest bucket.</p>
<p>If <code>b</code> has 0 observations, <code>NaN</code> is returned. If <code>b</code> contains fewer than two buckets, <code>NaN</code> is returned. For φ < 0, <code>-Inf</code> is returned. For φ > 1, <code>+Inf</code> is returned. For φ = <code>NaN</code>, <code>NaN</code> is returned.</p>
</div>
</div>
<!-- holt_winters() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-holt_winters">holt_winters()</h1>
<div class="introduce-view__content-label">
<p><code>holt_winters(v range-vector, sf scalar, tf scalar)</code> produces a smoothed value for time series based on the range in <code>v</code>. The lower the smoothing factor <code>sf</code>, the more importance is given to old data. The higher the trend factor <code>tf</code>, the more trends in the data is considered. Both <code>sf</code> and tf must be between 0 and 1.</p>
<p><code>holt_winters</code> should only be used with gauges.</p>
</div>
</div>
<!-- hour() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-hour">hour()</h1>
<div class="introduce-view__content-label">
<p><code>hour(v=vector(time()) instant-vector)</code> returns the hour of the day for each of the given times in UTC. Returned values are from 0 to 23.</p>
</div>
</div>
<!-- idelta() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-idelta">idelta()</h1>
<div class="introduce-view__content-label">
<p><code>idelta(v range-vector)</code> calculates the difference between the last two samples in the range vector <code>v</code>, returning an instant vector with the given deltas and equivalent labels.</p>
<p><code>idelta</code> should only be used with gauges.</p>
</div>
</div>
<!-- increase() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-increase">increase()</h1>
<div class="introduce-view__content-label">
<p><code>increase(v range-vector)</code> calculates the increase in the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments.</p>
<p>The following example expression returns the number of HTTP requests as measured over the last 5 minutes, per time series in the range vector:</p>
<pre>increase(http_requests_total{job="api-server"}[5m])</pre>
<p><code>increase</code> should only be used with counters. It is syntactic sugar for <code>rate(v)</code> multiplied by the number of seconds under the specified time range window, and should be used primarily for human readability. Use <code>rate</code> in recording rules so that increases are tracked consistently on a per-second basis.</p>
</div>
</div>
<!-- irate() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-irate">irate()</h1>
<div class="introduce-view__content-label">
<p><code>irate(v range-vector)</code> calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for.</p>
<p>The following example expression returns the per-second rate of HTTP requests looking up to 5 minutes back for the two most recent data points, per time series in the range vector:</p>
<pre>irate(http_requests_total{job="api-server"}[5m])</pre>
<p><code>irate</code> should only be used when graphing volatile, fast-moving counters. Use <code>rate</code> for alerts and slow-moving counters, as brief changes in the rate can reset the <code>FOR</code> clause and graphs consisting entirely of rare spikes are hard to read.</p>
<p>Note that when combining <code>irate()</code> with an aggregation operator (e.g. <code>sum()</code>) or a function aggregating over time (any function ending in <code>_over_time</code>), always take a <code>irate()</code> first, then aggregate. Otherwise <code>irate()</code> cannot detect counter resets when your target restarts.</p>
</div>
</div>
<!-- label_join() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-label_join">label_join()</h1>
<div class="introduce-view__content-label">
<p>For each timeseries in <code>v</code>, <code>label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)</code> joins all the values of all the <code>src_labels</code> using <code>separator</code> and returns the timeseries with the label <code>dst_label</code> containing the joined value. There can be any number of <code>src_labels</code> in this function.</p>
<p>This example will return a vector with each time series having a <code>foo</code> label with the value <code>a,b,c</code> added to it:</p>
<pre>label_join(up{job="api-server",src1="a",src2="b",src3="c"}, "foo", ",", "src1", "src2", "src3")</pre>
</div>
</div>
<!-- label_replace() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-label_replace">label_replace()</h1>
<div class="introduce-view__content-label">
<p>For each timeseries in <code>v</code>, <code>label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)</code> matches the regular expression <code>regex</code> against the value of the label <code>src_label</code>. If it matches, the value of the label <code>dst_label</code> in the returned timeseries will be the expansion of <code>replacement</code>, together with the original labels in the input. Capturing groups in the regular expression can be referenced with <code>$1</code>, <code>$2</code>, etc. If the regular expression doesn't match then the timeseries is returned unchanged.</p>
<p>This example will return timeseries with the values <code>a:c</code> at label <code>service</code> and <code>a</code> at label <code>foo</code>:</p>
<pre>label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")</pre>
</div>
</div>
<!-- ln() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-ln">ln()</h1>
<div class="introduce-view__content-label">
<p><code>ln(v instant-vector)</code> calculates the natural logarithm for all elements in <code>v</code>. Special cases are:</p>
<ul>
<li><code>ln(+Inf) = +Inf</code></li>
<li><code>ln(0) = -Inf</code></li>
<li><code>ln(x &lt; 0) = NaN</code></li>
<li><code>ln(NaN) = NaN</code></li>
</ul>
</div>
</div>
<!-- log2() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-log2">log2()</h1>
<div class="introduce-view__content-label">
<p><code>log2(v instant-vector)</code> calculates the binary logarithm for all elements in <code>v</code>. The special cases are equivalent to those in <code>ln</code>.</p>
</div>
</div>
<!-- log10() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-log10">log10()</h1>
<div class="introduce-view__content-label">
<p><code>log10(v instant-vector)</code> calculates the decimal logarithm for all elements in <code>v</code>. The special cases are equivalent to those in <code>ln</code>.</p>
</div>
</div>
<!-- minute() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-minute">minute()</h1>
<div class="introduce-view__content-label">
<p><code>minute(v=vector(time()) instant-vector)</code> returns the minute of the hour for each of the given times in UTC. Returned values are from 0 to 59.</p>
</div>
</div>
<!-- month() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-month">month()</h1>
<div class="introduce-view__content-label">
<p><code>month(v=vector(time()) instant-vector)</code> returns the month of the year for each of the given times in UTC. Returned values are from 1 to 12, where 1 means January etc.</p>
</div>
</div>
<!-- predict_linear() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-predict_linear">predict_linear()</h1>
<div class="introduce-view__content-label">
<p><code>predict_linear(v range-vector, t scalar)</code> predicts the value of time series <code>t</code> seconds from now, based on the range vector <code>v</code>, using simple linear regression. The range vector must have at least two samples in order to perform the calculation. When <code>+Inf</code> or <code>-Inf</code> are found in the range vector, the slope and offset value calculated will be <code>NaN</code>.</p>
<p><code>predict_linear</code> should only be used with gauges.</p>
</div>
</div>
<!-- rate() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-rate">rate()</h1>
<div class="introduce-view__content-label">
<p><code>rate(v range-vector)</code> calculates the per-second average rate of increase of the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. Also, the calculation extrapolates to the ends of the time range, allowing for missed scrapes or imperfect alignment of scrape cycles with the range's time period.</p>
<p>The following example expression returns the per-second rate of HTTP requests as measured over the last 5 minutes, per time series in the range vector:</p>
<pre>rate(http_requests_total{job="api-server"}[5m])</pre>
<p><code>rate</code> should only be used with counters. It is best suited for alerting, and for graphing of slow-moving counters.</p>
<p>Note that when combining <code>rate()</code> with an aggregation operator (e.g. <code>sum()</code>) or a function aggregating over time (any function ending in <code>_over_time</code>), always take a <code>rate()</code> first, then aggregate. Otherwise <code>rate()</code> cannot detect counter resets when your target restarts.</p>
</div>
</div>
<!-- resets() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-resets">resets()</h1>
<div class="introduce-view__content-label">
<p>For each input time series, <code>resets(v range-vector)</code> returns the number of counter resets within the provided time range as an instant vector. Any decrease in the value between two consecutive samples is interpreted as a counter reset.</p>
<p><code>resets</code> should only be used with counters.</p>
</div>
</div>
<!-- round() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-round">round()</h1>
<div class="introduce-view__content-label">
<p><code>round(v instant-vector, to_nearest=1 scalar)</code> rounds the sample values of all elements in <code>v</code> to the nearest integer. Ties are resolved by rounding up. The optional <code>to_nearest</code> argument allows specifying the nearest multiple to which the sample values should be rounded. This multiple may also be a fraction.</p>
</div>
</div>
<!-- scalar() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-scalar">scalar()</h1>
<div class="introduce-view__content-label">
<p>Given a single-element input vector, <code>scalar(v instant-vector)</code> returns the sample value of that single element as a scalar. If the input vector does not have exactly one element, <code>scalar</code> will return <code>NaN</code>.</p>
</div>
</div>
<!-- sgn() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-sgn">sgn()</h1>
<div class="introduce-view__content-label">
<p><code>sgn(v instant-vector)</code> returns a vector with all sample values converted to their sign, defined as this: 1 if v is positive, -1 if v is negative and 0 if v is equal to zero.</p>
</div>
</div>
<!-- sort() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-sort">sort()</h1>
<div class="introduce-view__content-label">
<p><code>sort(v instant-vector)</code> returns vector elements sorted by their sample values, in ascending order.</p>
</div>
</div>
<!-- sort_desc() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-sort_desc">sort_desc()</h1>
<div class="introduce-view__content-label">
<p>Same as <code>sort</code>, but sorts in descending order.</p>
</div>
</div>
<!-- sqrt() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-sqrt">sqrt()</h1>
<div class="introduce-view__content-label">
<p><code>sqrt(v instant-vector)</code> calculates the square root of all elements in <code>v</code>.</p>
</div>
</div>
<!-- time() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-time">time()</h1>
<div class="introduce-view__content-label">
<p><code>time()</code> returns the number of seconds since January 1, 1970 UTC. Note that this does not actually return the current time, but the time at which the expression is to be evaluated.</p>
</div>
</div>
<!-- timestamp() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-timestamp">timestamp()</h1>
<div class="introduce-view__content-label">
<p><code>timestamp(v instant-vector)</code> returns the timestamp of each of the samples of the given vector as the number of seconds since January 1, 1970 UTC.</p>
</div>
</div>
<!-- vector() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-vector">vector()</h1>
<div class="introduce-view__content-label">
<p><code>vector(s scalar)</code> returns the scalar <code>s</code> as a vector with no labels.</p>
</div>
</div>
<!-- year() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-year">year()</h1>
<div class="introduce-view__content-label">
<p><code>year(v=vector(time()) instant-vector)</code> returns the year for each of the given times in UTC.</p>
</div>
</div>
<!-- <aggregation>_over_time() -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-aggregation">&lt;aggregation&gt;_over_time()</h1>
<div class="introduce-view__content-label">
<p>The following functions allow aggregating each series of a given range vector over time and return an instant vector with per-series aggregation results:</p>
<ul>
<li><code>avg_over_time(range-vector):</code> the average value of all points in the specified interval.</li>
<li><code>min_over_time(range-vector):</code> the minimum value of all points in the specified interval.</li>
<li><code>max_over_time(range-vector):</code> the maximum value of all points in the specified interval.</li>
<li><code>sum_over_time(range-vector):</code> the sum of all values in the specified interval.</li>
<li><code>count_over_time(range-vector):</code> the count of all values in the specified interval.</li>
<li><code>quantile_over_time(scalar, range-vector):</code> the φ-quantile (0 φ 1) of the values in the specified interval.</li>
<li><code>stddev_over_time(range-vector):</code> the population standard deviation of the values in the specified interval.</li>
<li><code>stdvar_over_time(range-vector):</code> the population standard variance of the values in the specified interval.</li>
<li><code>last_over_time(range-vector):</code> the most recent point value in specified interval.</li>
<li><code>present_over_time(range-vector):</code> the value 1 for any series in the specified interval.</li>
</ul>
<p>Note that all values in the specified interval have the same weight in the aggregation even if the values are not equally spaced throughout the interval.</p>
</div>
</div>
<!-- Trigonometric Functions -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="functions-trigonometric">Trigonometric Functions</h1>
<div class="introduce-view__content-label">
<p>The trigonometric functions work in radians:</p>
<ul>
<li><code>acos(v instant-vector):</code> calculates the arccosine of all elements in v (special cases).</li>
<li><code>acosh(v instant-vector):</code> calculates the inverse hyperbolic cosine of all elements in v (special cases).</li>
<li><code>asin(v instant-vector):</code> calculates the arcsine of all elements in v (special cases).</li>
<li><code>asinh(v instant-vector):</code> calculates the inverse hyperbolic sine of all elements in v (special cases).</li>
<li><code>atan(v instant-vector):</code> calculates the arctangent of all elements in v (special cases).</li>
<li><code>atanh(v instant-vector):</code> calculates the inverse hyperbolic tangent of all elements in v (special cases).</li>
<li><code>cos(v instant-vector):</code> calculates the cosine of all elements in v (special cases).</li>
<li><code>cosh(v instant-vector):</code> calculates the hyperbolic cosine of all elements in v (special cases).</li>
<li><code>sin(v instant-vector):</code> calculates the sine of all elements in v (special cases).</li>
<li><code>sinh(v instant-vector):</code> calculates the hyperbolic sine of all elements in v (special cases).</li>
<li><code>tan(v instant-vector):</code> calculates the tangent of all elements in v (special cases).</li>
<li><code>tanh(v instant-vector):</code> calculates the hyperbolic tangent of all elements in v (special cases).</li>
</ul>
<p>The following are useful for converting between degrees and radians:</p>
<ul>
<li><code>deg(v instant-vector)</code>: converts radians to degrees for all elements in <code>v</code>.</li>
<li><code>pi()</code>: returns pi.</li>
<li><code>rad(v instant-vector)</code>: converts degrees to radians for all elements in <code>v</code>.</li>
</ul>
</div>
</div>
<!-- QUERY EXAMPLES -->
<h1 class="page-header-one" id="query-examples">Query examples</h1>
<div class="introduce-view__content">
<h1 class="page-header-two" id="simple-time-series-selection">Simple time series selection</h1>
<div class="introduce-view__content-label">
<p>Return all time series with the metric <code>http_requests_total</code>:</p>
<pre>http_requests_total</pre>
<p>Return all time series with the metric <code>http_requests_total</code> and the given <code>job</code> and <code>handler</code> labels:</p>
<pre>http_requests_total{job="apiserver", handler="/api/comments"}</pre>
<p>Return a whole range of time (in this case 5 minutes up to the query time) for the same vector, making it a <b style="color: #3C92F1" @click="jumpClick('#range-vector-selectors')" class="log-link">range vector</b>:</p>
<pre>http_requests_total{job="apiserver", handler="/api/comments"}[5m]</pre>
<p>Note that an expression resulting in a range vector cannot be graphed directly, but viewed in the tabular ("Console") view of the expression browser.</p>
<p>Using regular expressions, you could select time series only for jobs whose name match a certain pattern, in this case, all jobs that end with <code>server</code>:</p>
<pre>http_requests_total{job=~".*server"}</pre>
<p>All regular expressions in Prometheus use RE2 syntax.</p>
<p>To select all HTTP status codes except 4xx ones, you could run:</p>
<pre>http_requests_total{status!~"4.."}</pre>
</div>
</div>
<!-- Subquery -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="query-examples-subquery">Subquery</h1>
<div class="introduce-view__content-label">
<p>Return the 5-minute rate of the <code>http_requests_total</code> metric for the past 30 minutes, with a resolution of 1 minute.</p>
<pre>rate(http_requests_total[5m])[30m:1m]</pre>
<p>This is an example of a nested subquery. The subquery for the <code>deriv</code> function uses the default resolution. Note that using subqueries unnecessarily is unwise.</p>
<pre>max_over_time(deriv(rate(distance_covered_total[5s])[30s:5s])[10m:])</pre>
</div>
</div>
<!-- Using functions, operators, etc. -->
<div class="introduce-view__content">
<h1 class="page-header-two" id="using-functions">Using functions, operators, etc.</h1>
<div class="introduce-view__content-label">
<p>Return the per-second rate for all time series with the <code>http_requests_total</code> metric name, as measured over the last 5 minutes:</p>
<pre>rate(http_requests_total[5m])</pre>
<p>Assuming that the <code>http_requests_total</code> time series all have the labels <code>job</code> (fanout by job name) and <code>instance</code> (fanout by instance of the job), we might want to sum over the rate of all instances, so we get fewer output time series, but still preserve the <code>job</code> dimension:</p>
<pre>sum by (job) (
rate(http_requests_total[5m])
)</pre>
<p>If we have two different metrics with the same dimensional labels, we can apply binary operators to them and elements on both sides with the same label set will get matched and propagated to the output. For example, this expression returns the unused memory in MiB for every instance (on a fictional cluster scheduler exposing these metrics about the instances it runs):</p>
<pre>(instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024</pre>
<p>The same expression, but summed by application, could be written like this:</p>
<pre>sum by (app, proc) (
instance_memory_limit_bytes - instance_memory_usage_bytes
) / 1024 / 1024</pre>
<p>If the same fictional cluster scheduler exposed CPU usage metrics like the following for every instance:</p>
<pre>instance_cpu_time_ns{app="lion", proc="web", rev="34d0f99", env="prod", job="cluster-manager"}
2021-08-02 19:51:53 +08:00
instance_cpu_time_ns{app="elephant", proc="worker", rev="34d0f99", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="turtle", proc="api", rev="4d3a513", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="fox", proc="widget", rev="4d3a513", env="prod", job="cluster-manager"}
...</pre>
<p>...we could get the top 3 CPU users grouped by application (<code>app</code>) and process type (<code>proc</code>) like this:</p>
<pre>topk(3, sum by (app, proc) (rate(instance_cpu_time_ns[5m])))</pre>
<p>Assuming this metric contains one time series per running instance, you could count the number of running instances per application like this:</p>
<pre>count by (app) (instance_cpu_time_ns)</pre>
</div>
</div>
</div>
</div>
</div>
<div v-else v-show="showIntroduce" class="introduce-view">
<div class="info-room title-heard">
<div class="col-md-9 logs-content">
<!-- LogQL: Log query language -->
<h1 class="page-header" style="margin-Top:0px" id="log-query-language">LogQL: Log Query Language<a class="header-anchor" href="https://grafana.com/docs/loki/latest/logql/" rel="noopener noreferrer" target="_blank"><i class="nz-icon nz-icon-link1" style="font-size: 16px;" :title="$t('overall.link')"></i></a></h1>
<div class="title-heard__divider"></div>
<!-- catalog 目录 -->
<div class="catalog">
<ul class="catalog-square">
<li>
<div @click="jumpClick('#log-queries')"><span>Log queries</span></div>
<ul class="catalog-disc">
<li @click="jumpClick('#log-stream-selector')"><span>Log stream selector</span></li>
<li>
<div @click="jumpClick('#log-pipeline')"><span>Log pipeline</span></div>
<ul>
<li @click="jumpClick('#line-filter-expressions')"><span>Line filter expression</span></li>
<li @click="jumpClick('#label-filter-expressions')"><span>Label filter expression</span></li>
<li @click="jumpClick('#parsing-expressions')"><span>Parser expression</span></li>
<li @click="jumpClick('#JSON')"><span>JSON</span></li>
<li @click="jumpClick('#logfmt')"><span>logfmt</span></li>
<li @click="jumpClick('#pattern')"><span>Pattern</span></li>
<li @click="jumpClick('#regexp')"><span>Regular expression</span></li>
<li @click="jumpClick('#unpack')"><span>unpack</span></li>
<li @click="jumpClick('#line-format-expressions')"><span>Line format expression</span></li>
<li @click="jumpClick('#label-format-expressions')"><span>Labels format expression</span></li>
</ul>
</li>
<li>
<div @click="jumpClick('#log-queries-examples')"><span>Log queries examples</span></div>
<ul>
<li @click="jumpClick('#multiple-filtering')"><span>Multiple filtering</span></li>
<li @click="jumpClick('#multiple-parsers')"><span>Multiple parsers</span></li>
<li @click="jumpClick('#formatting')"><span>Formatting</span></li>
</ul>
</li>
</ul>
</li>
<li>
<div @click="jumpClick('#metric-queries')"><span>Metric queries</span></div>
<ul>
<li style="list-style:disc;">
<div @click="jumpClick('#range-vector-aggregation')"><span>Range Vector aggregation</span></div>
<ul>
<li @click="jumpClick('#log-range-aggregations')"><span>Log range aggregations</span></li>
<li @click="jumpClick('#unwrapped-range-aggregations')"><span>Unwrapped range aggregations</span></li>
<li @click="jumpClick('#unwrapped-examples')"><span>Unwrapped examples</span></li>
</ul>
</li>
<li style="list-style:disc;">
<div @click="jumpClick('#built-in-aggregation-operators')"><span>Built-in aggregation operators</span></div>
<ul><li @click="jumpClick('#vector-aggregation-examples')"><span>Vector aggregation examples</span></li></ul>
</li>
</ul>
</li>
</ul>
</div>
<div class="page-header-label">
<p>LogQL is Grafana Lokis PromQL-inspired query language. Queries act as if they are a distributed grep to aggregate log sources. LogQL uses labels and operators for filtering.</p>
<p>There are two types of LogQL queries:</p>
<ul>
2022-09-01 11:06:16 +08:00
<li><b style="color: #3C92F1" @click="jumpClick('#log-queries')" class="log-link">Log queries </b>return the contents of log lines.</li>
<li><b style="color: #3C92F1" @click="jumpClick('#metric-queries')" class="log-link">Metric queries </b>extend log queries to calculate values based on query results.</li>
</ul>
</div>
<!-- Log queries -->
<!-- start -->
<div class="introduce-view__content">
2022-09-01 11:06:16 +08:00
<h1 class="page-header-one" id="log-queries">Log queries</h1>
<p>All LogQL queries contain a <b>log stream selector.</b></p>
2022-09-01 11:06:16 +08:00
<div class="img-hidden"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAkYAAAIPCAYAAACWihHIAABZK0lEQVR4XuzdC7ylU/3H8Z9b5JJE7pdBSDepSAmDoitdpFBModCFVFL/MhJKiopIxSil+82llDLjTq6JEJlBuadE7tX/953fWmevvc6zz+wzc2Zm77M/79fr9zJ7Pes8+9n7nP//+bbWep7HDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNiHa9V60aMudW81q4b55GVvNarGwEAQDudMB/zus9rwWrb/HSJ17F1Y+UlXtO9Xl1v6EGLeP3D61Gv5attY0371/fynqLtb15PeE0o2gAAGGjXeh1RtS3m9Suvk6v2+U0n8h/UjZVXeP3P6831hh71Ha8zvJ5SbxhjK1t8Lx8p2r7mdbbXkkUbAAAD7V9e36wbe9R4DEbzSlMwAgCgby3ktZ3Xoam2T221g7wmei1qMW3yea8DvNYs+sgWXpMtpnGuSP/evdj+Aa+ditfZM7z2thhl+rjXBu2bZ3q2xf5W8HqWRT8dxySL42qiKbAPeR3l9VFrXvsyp8FoQ69PWBz7Xl5Pb9/cZiOvT1kc9w6pbRevPYd6NNPoiz77i72e5vV+i33sbzFFWdvV4vvMyu9uDa+PWfz8Hl6LF/1q23od5nWk1zstRv1KTcFoR6/9itf6G9F7r27x3gfarN97GYvv8ksW3+0m7ZsBABh7OqEqvOjEpnCg0r+vtDjhlf7j9VmvCy1Gg6Z7PWyxlqQ8qX8wbVP/3O+nxfabvE4vXotOvg9Y7Ev9tT5Gx3GMta9FemNq13vovWd43ZvaLrX2cKRwd1Ladr/XVRb7fdJrt6KfzG4w0rEdn9q1bx27PsM/vV5Z9JMFrNVX29VX6610jJpevKzVtdGKFj+rMPona73f4xbfhb6b0m+9rile5+/uHRbvf5fXrantdq/ntLrOpMDyC4vt6jsj/fsvFqE0awpGP7Hon21l0UchTn8Tt3ndndr+aMOn3F5qsT3/PWhNmvpqBFLfIwAAY04nmN95Pei1TdGuE7pCyrmpT6ag84jF2pV8IlvO4qSuE5hGTUqdptLqYLSGRd/fW1xJJVo8/BmLk2E58pBP7jqR5/dTONFIl9rLERKNmKhNIw45MD3V63yLz6d/Z7MbjHSiV9shFscsGhVRyNF75M8jCo/qq5GrfDy6Ok99NbrWbTDS76D8TNqHQuFDFt9l1ikYqd+7inaNYCn4XGetzyBajK730mhWpu9c35WCc/7bGE0wUth5edGuUSu1awQp00jRnRaBfa3UtrBFH/XdJ7UBADCm9L/KdaLRyb2m6S5t27RoUzC6x4ZPfSgc/dtrStXebTDSlIqCVdOl5VMtTsR51Cif3Pcd6hF04tQoyKlV+wts+NVvkyz28cKibXaCkUakdAI/Z6hHi0ZUNDKlEbZMozwatapHPNb3+q91H4xusOGfaR2L34+mvLJOwejbRVumUSRte0N6vazFSNSnh3q07GzRNwec0QQjTSGW9F3ou9ci8ezD1vmKNv09aFE/AABjTus2dLJapd5gsQ5F27SuKNOJtw4e2Vlet1Rt3Qajyy0ul2+iESAdx3PT63xy14m2dr3F6FUTjUK8zGLtyy8t9rFZsX12gtHz0+v3DvVopxEwlShoqO/k1uY2CjvdBqMv1BsShaALitedglFe11RayuL3m/f9Jou+r7MIKGXpe9Q2hWcZTTDSfmv63Wt6NtPvR8c9oaG+bBEiy9E+AADGhE4y+l/mnWga5avFa504NQ3U5ESLKZpSt8FI02KdQolOzDqhbp1e55P75kM9WjQio4CWaVpIwW66xc+oNLKlADUWwehV6fVrhnq0+5HFGh7Rwmf17RSipln3wagMICWFwj8XrzsFI32OJlqzlEeTciAdqXJoHk0wen3Rll3sdVHxWtN09XvV1bTYHACAOZLX5SxRb7BYv6L/Zf65ok3B6BvF65JCwB1VW7fBSIGm00iPrl7TMWodjIwmGH3Rou/JFlNEWvsjeTRkToNRnop821CPdr+21rSP1hqpr0bpmmgBcrfBqGl6SzTqotG3rFMwagpymhbU1JnWFck7Lfpua8NHbXI93cJYB6NzLcLRhBGq6apJAADmyFstTlYalalpMba2aT1JpmCkBbo1re9RsPhN1a5gpJGkWh2Mvm9x1VjT9MgJFguTdXm6jCYYafREJ9mabjUwFsFIwUDHdvxQjxZ9lnLN0wIWwVEhoDbB4rvtNhidXW+wmArTlWknF22dgpEWtdfyZ9Pl8aL1V3o9KXcYwVgHI41S6m+nvi0AAABzlUaKdDWSFgSXo0ZaXK2TtK4g0gk308lbJ7fdijbJVwtplKGkfddhSepglENYOTolugIqXwWXjSYYafREIzbl6II+j8LCWAQjUfBRICkXcssRFn3LS/a1qFhtWpCtETnR+i6N9ChgdRuMNJJXXkUomhbVtjzlKJ2CkS59n1C061jOs7g6cfmiXaM2Wjem9y0dbBF61k2vxzoYbWzxt6bPVNLvTsH8u1U7AABj5tUW4WO6xcLbIy3uU6O2eiRJJ6vTLE6smjqbbDEFphOerirSqEhJ02japlEbXc6e1cFIjrHoO81iqkg/q/VAN1r7yXo0wWhPi7468epYFVZuTa/VPhbBSKFBn0fHqmPW59TnVb+ji36iK8lOStu0Hkvfub7Tr1kEk26D0Q8tRlS+ZfFd6WfVXk9bdgpGp1jc++krFmFUa64UtnSzxZKu6FM/lUZxyt/3j232LtfvJhiJ3kv9L7WY8tWViwppmu7TtCgAAHONRjv0v8J1gr/ZYhSkHgERncS/ZHHn5XMsTuwabdKIkRY61zQVomkbnfB1Ms80AnR48TrbxeJybO33DxYn7byOJVM4UR+dtGsnW5xAS5Msrgy7zSIk6EoqhSrt4/mtbjNP9DoZj0T99XN1KHuGxftq/zp2fTdvb+vRTuul9J3p/XJQGE0wOsAiaGikSe+nz6d7+9TBVGusyqnMHIy2sAiNWtekn9fUXNO6I1nDYl3ZDRZ9FWLeZ+2jcLriTt9LudZKAVFTpNmLLProiraaQlde21TSWjAdm95Xpf3ltWYAAMx3ORhh9jWtoxKN2jSthyqVwWh2lMEIAADMIYLRnHmvxQ0hNXpS2tUisBxWtdcIRgAA9BCC0ZxZ3+KeTbp31PkWdwq/2iKsaI1NudC9CcEIAIAecpDFYm3MPt12QGt0tI5Li6N1OwLdq6mb+/IsabEuadN6Q5d0k0n9/Br1BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9aUGvG70WqDcAAAAMmqUtnmq/RL0BAABg0KzodYnFyBEAAAAAAAAAAACAIS/w2sbrd/UGAACAQbKY1/1eq6X/Lt++GQAAYHDs4PXb9O+ve32y2AYAADBQLvB6S/r3+l4/L7YBAAAMjEW8DjMu0wcAAAAAAIAsWzcUVvD6jdfT6g0AAADjjRZcX+e1UL2hcIzXmRbTbQAAAOPS1l53e21Qb6gs7PUTi8XYIwUoAACAvrS6111em9YbOlA42rVuBAAAGC/0wNjZpWAFAADQt/Soj13qxtmg6bQ/eP3Ma71qGwAAQM/Sgukdvc7zut7rde2bZ9tTvA6wmI47utoGAAAw3+nmjBO8NvNaILUt4XWGxR2ttU5orOn5auXi7c29PmBxDBOMxdoAAAw50WLK5W9e//N6YbHt6tSWS091z66ptj2v2HZtte05xTZddl5u02MtMo2WlNvKKaAbq23rFNtuqratXWy7udq2VrHtlmrbhGLbjGpbuV7ntmrbqsW226ttqxTb9B0/4TXd63yvZYpt85IC0bEWjxfRsXyk2PY5az/+jxbbjqi2lT93ZLVt/2LbF6tt+xXbNJJVbvtgse1L1TaFuewr1
<p>Optionally, the log stream selector can be followed by <b>a log pipeline</b>. A log pipeline is a set of stage expressions that are chained together and applied to the selected log streams. Each expression can filter out, parse, or mutate log lines and their respective labels.</p>
<p>The following example shows a full log query in action:</p>
<p><code class="fillbox">{container="query-frontend",namespace="loki-dev"} |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500</code></p>
<p>The query is composed of:</p>
<ul>
<li>a log stream selector <code>{container="query-frontend",namespace="loki-dev"}</code> which targets the <code>query-frontend</code> container in the <code>loki-dev</code> namespace.</li>
<li>a log pipeline <code>|= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500</code> which will filter out log that contains the word <code>metrics.go</code>, then parses each log line to extract more labels and filter with them.</li>
</ul>
<pre>To avoid escaping special characters you can use the <code>`</code>(backtick) instead of<code> " </code>when quoting strings. For example <code>`\w+`</code> is the same as <code>"\\w+"</code>. This is specially useful when writing a regular expression which contains multiple <br/>backslashes that require escaping.</pre>
</div>
<!-- Log stream selector -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h1 class="page-header-two" id="log-stream-selector">Log stream selector</h1>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
<p>The stream selector determines which log streams to include in a querys results. A log stream is a unique source of log content, such as a file. A more granular log stream selector then reduces the number of searched streams to a manageable volume. This means that the labels passed to the log stream selector will affect the relative performance of the querys execution.</p>
<p>The log stream selector is specified by one or more comma-separated key-value pairs. Each key is a log label and each value is that labels value. Curly braces (<code>{</code> and <code>}</code>) delimit the stream selector.</p>
<p>Consider this stream selector:</p>
<p><code class="fillbox">{app="mysql",name="mysql-backup"}</code></p>
<p>All log streams that have both a label of <code>app</code> whose value is <code>mysql</code> and a label of <code>name</code> whose value is <code>mysql-backup</code> will be included in the query results. A stream may contain other pairs of labels and values, but only the specified pairs within the stream selector are used to determine which streams will be included within the query results.</p>
<p>The same rules that apply for Prometheus Label Selectors apply for Grafana Loki log stream selectors.</p>
<p>The <code>=</code> operator after the label name is a <b>label matching operator</b>. The following label matching operators are supported:</p>
<ul>
<li><code>=</code>: exactly equal</li>
<li><code>!=</code>: not equal</li>
<li><code>=~</code>: regex matches</li>
<li><code>!~</code>: regex does not match</li>
</ul>
<p>Regex log stream examples:</p>
<ul>
<li><code>{name =~ "mysql.+"}</code></li>
<li><code>{name !~ "mysql.+"}</code></li>
<li><code>{name !~ `mysql-\d+`}</code></li>
</ul>
<p><b>Note:</b> The <code>=~</code> regex operator is fully anchored, meaning regex must match against the entire string, including newlines. The regex <code>.</code> character does not match newlines by default. If you want the regex dot character to match newlines you can use the single-line flag, like so: <code>(?s)search_term.+</code> matches <code>search_term\n.</code></p>
2021-08-02 19:51:53 +08:00
</div>
</div>
<!-- Log pipeline -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h1 class="page-header-two" id="log-pipeline">Log pipeline</h1>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
<p>A log pipeline can be appended to a log stream selector to further process and filter log streams. It is composed of a set of expressions. Each expression is executed in left to right sequence for each log line. If an expression filters out a log line, the pipeline will stop processing the current log line and start processing the next log line.</p>
<p>Some expressions can mutate the log content and respective labels, which will be then be available for further filtering and processing in subsequent expressions. An example that mutates is the expression</p>
<p><code v-pre class="fillbox">| line_format "{{.status_code}}"</code></p>
<p>Log pipeline expressions fall into one of three categories:</p>
<ul>
2022-09-01 11:06:16 +08:00
<li>Filtering expressions: <b style="color: #3C92F1" @click="jumpClick('#line-filter-expressions')" class="log-link">line filter expressions</b> and <b style="color: #3C92F1" @click="jumpClick('#label-filter-expressions')" class="log-link">label filter expressions</b></li>
<li><b style="color: #3C92F1" @click="jumpClick('#parsing-expressions')" class="log-link">Parsing expressions</b></li>
<li>Formatting expressions: <b style="color: #3C92F1" @click="jumpClick('#line-format-expressions')" class="log-link">line format expressions</b> and <b style="color: #3C92F1" @click="jumpClick('#label-format-expressions')" class="log-link">label format expressions</b></li>
</ul>
2021-08-02 19:51:53 +08:00
</div>
</div>
<!-- Line filter expression -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
<h2 id="line-filter-expressions">Line filter expression</h2>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
<p>The line filter expression does a distributed <code>grep</code> over the aggregated logs from the matching log streams. It searches the contents of the log line, discarding those lines that do not match the case sensitive expression.</p>
<p>Each line filter expression has a <b>filter operator</b> followed by text or a regular expression. These filter operators are supported:</p>
<ul>
<li><code>|=</code>: Log line contains string</li>
<li><code>!=</code>: Log line does not contain string</li>
<li><code>|~</code>: Log line contains a match to the regular expression</li>
<li><code>!~</code>: Log line does not contain a match to the regular expression</li>
</ul>
<p>Line filter expression examples:</p>
<ul>
<li>
<p>Keep log lines that have the substring error:</p>
<p><code class="fillbox">|= "error"</code></p>
<p>A complete query using this example:</p>
<p><code class="fillbox">{job="mysql"} |= "error"</code></p>
</li>
<li>
<p>Discard log lines that have the substring kafka.server:type=ReplicaManager:</p>
<p><code class="fillbox">!= "kafka.server:type=ReplicaManager"</code></p>
<p>A complete query using this example:</p>
<p><code class="fillbox">{instance=~"kafka-[23]",name="kafka"} != "kafka.server:type=ReplicaManager"</code></p>
</li>
<li>
2022-09-01 11:46:31 +08:00
<p>Keep log lines that contain a substring that starts with <code>tsdb-ops</code> and ends with <code>io:2003</code>. A complete query with a regular expression:</p>
<p><code class="fillbox">{name="kafka"} |~ "tsdb-ops.*io:2003"</code></p>
</li>
<li>
2022-09-01 11:46:31 +08:00
<p>Keep log lines that contain a substring that starts with <code>error=</code>, and is followed by 1 or more word characters. A complete query with a regular expression:</p>
<p><code class="fillbox">{name="cassandra"} |~ `error=\w+`</code></p>
</li>
</ul>
<p>Filter operators can be chained. Filters are applied sequentially. Query results will have satisfied every filter. This complete query example will give results that include the string <code>error</code>, and do not include the string <code>timeout</code>.</p>
<p><code class="fillbox">{job="mysql"} |= "error" != "timeout"</code></p>
<p>When using <code>|~</code> and <code>!~</code>, Go (as in Golang) RE2 syntax regex may be used. The matching is case-sensitive by default. Switch to case-insensitive matching by prefixing the regular expression with <code>(?i)</code>.</p>
<p>While line filter expressions could be placed anywhere within a log pipeline, it is almost always better to have them at the beginning. Placing them at the beginning improves the performance of the query, as it only does further processing when a line matches. For example, while the results will be the same, the query specified with</p>
<p><code class="fillbox" v-pre>{job="mysql"} |= "error" | json | line_format "{{.err}}"</code></p>
<p>will always run faster than</p>
<p><code class="fillbox" v-pre>{job="mysql"} | json | line_format "{{.message}}" |= "error"</code></p>
<p>Line filter expressions are the fastest way to filter logs once the log stream selectors have been applied.</p>
<p>Line filter expressions have support matching IP addresses. See Matching IP addresses for details.</p>
2021-08-02 19:51:53 +08:00
</div>
</div>
<!-- Label filter expression -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
<h2 id="label-filter-expressions">Label filter expression</h2>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
<p>Label filter expression allows filtering log line using their original and extracted labels. It can contain multiple predicates.</p>
<p>A predicate contains a <b>label identifier</b>, an <b>operation</b> and a <b>value</b> to compare the label with.</p>
<p>For example with <code>cluster="namespace"</code>the cluster is the label identifier, the operation is <code>=</code> and the value is namespace. The label identifier is always on the right side of the operation.</p>
<p>We support multiple <b>value</b> types which are automatically inferred from the query input.</p>
<ul>
<li><b>String </b>is double quoted or backticked such as <code>"200"</code> or `<code>us-central1</code>`.</li>
<li>Duration is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as 300ms, 1.5h or 2h45m. Valid time units are ns, us (or µs), ms, s, m, h.</li>
<li><b>Number </b>are floating-point number (64bits), such as <code>250</code>, <code>89.923</code>.</li>
<li><b>Bytes </b>is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as 42MB, 1.5Kib or 20b. Valid bytes units are b, kib, kb, mib, mb, gib, gb, tib, tb, pib, pb, eib, eb.</li>
</ul>
2022-09-01 11:06:16 +08:00
<p>String type work exactly like Prometheus label matchers use in <b style="color: #3C92F1" @click="jumpClick('#log-stream-selector')" class="log-link">log stream selector</b>. This means you can use the same operations (<code>=</code>,<code>!=</code>,<code>=~</code>,<code>!~</code>).</p>
<pre>The string type is the only one that can filter out a log line with a label <code>__error__</code>.</pre>
<p>Using Duration, Number and Bytes will convert the label value prior to comparision and support the following comparators:</p>
<ul>
<li><code>==</code> or <code>=</code> for equality.</li>
<li><code>!=</code> for inequality.</li>
<li><code>></code> and <code>>=</code> for greater than and greater than or equal.</li>
<li><code>&lt;</code> and <code>&lt;=</code> for lesser than and lesser than or equal.</li>
</ul>
<p>For instance, <code>logfmt | duration > 1m and bytes_consumed > 20MB</code></p>
<p>If the conversion of the label value fails, the log line is not filtered and an <code>__error__</code> label is added. To filters those errors see the pipeline errors section.</p>
<p>You can chain multiple predicates using <code>and</code> and <code>or</code> which respectively express the <code>and</code> and <code>or</code> binary operations. <code>and</code> can be equivalently expressed by a comma, a space or another pipe. Label filters can be place anywhere in a log pipeline.</p>
<p>This means that all the following expressions are equivalent:</p>
<p><code class="fillbox">
| duration >= 20ms or size == 20kb and method!~"2.."<br/>
| duration >= 20ms or size == 20kb | method!~"2.."<br/>
| duration >= 20ms or size == 20kb , method!~"2.."<br/>
| duration >= 20ms or size == 20kb method!~"2.."
</code></p>
<p>By default the precedence of multiple predicates is right to left. You can wrap predicates with parenthesis to force a different precedence left to right.</p>
<p>For example the following are equivalent.</p>
<p><code class="fillbox">
| duration >= 20ms or method="GET" and size &lt;= 20KB<br/>
| ((duration >= 20ms or method="GET") and size &lt;= 20KB)
</code></p>
<p>It will evaluate first <code>duration >= 20ms or method="GET"</code>. To evaluate first <code>method="GET" and size &lt;= 20KB</code>, make sure to use proper parenthesis as shown below.</p>
<p><code class="fillbox">| duration >= 20ms or (method="GET" and size &lt;= 20KB)</code></p>
<pre>Label filter expressions are the only expression allowed after the unwrap expression. This is mainly to allow filtering errors from the metric extraction.</pre>
<p>Label filter expressions have support matching IP addresses. See Matching IP addresses for details.</p>
2021-08-02 19:51:53 +08:00
</div>
</div>
<!-- Parser expression -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
<h2 id="parsing-expressions">Parser expression</h2>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
2022-09-01 11:06:16 +08:00
<p>Parser expression can parse and extract labels from the log content. Those extracted labels can then be used for filtering using <b style="color: #3C92F1" @click="jumpClick('#label-filter-expressions')" class="log-link">label filter expressions</b> or for <b style="color: #3C92F1" @click="jumpClick('#metric-queries')" class="log-link">metric aggregations</b>.</p>
<p>Extracted label keys are automatically sanitized by all parsers, to follow Prometheus metric name convention.(They can only contain ASCII letters and digits, as well as underscores and colons. They cannot start with a digit.)</p>
<p>For instance, the pipeline <code>| json</code> will produce the following mapping:</p>
<p><code class="fillbox">{ "a.b": {c: "d"}, e: "f" }</code></p>
<p>-></p>
<p><code class="fillbox">{a_b_c="d", e="f"}</code></p>
<p>In case of errors, for instance if the line is not in the expected format, the log line wont be filtered but instead will get a new <code>__error__</code> label added.</p>
2022-09-01 11:06:16 +08:00
<p>If an extracted label key name already exists in the original log stream, the extracted label key will be suffixed with the <code>_extracted</code> keyword to make the distinction between the two labels. You can forcefully override the original label using a <b style="color: #3C92F1" @click="jumpClick('#label-format-expressions')" class="log-link">label formatter expression</b>. However if an extracted key appears twice, only the latest label value will be kept.</p>
<p>Loki supports <b style="color: #3C92F1" @click="jumpClick('#JSON')" class="log-link">JSON</b>, <b style="color: #3C92F1" @click="jumpClick('#logfmt')" class="log-link">logfmt</b>, <b style="color: #3C92F1" @click="jumpClick('#pattern')" class="log-link">pattern</b>, <b style="color: #3C92F1" @click="jumpClick('#regexp')" class="log-link">regexp</b> and <b style="color: #3C92F1" @click="jumpClick('#unpack')" class="log-link">unpack</b> parsers.</p>
<p>Its easier to use the predefined parsers <code>json</code> and <code>logfmt</code> when you can. If you cant, the <code>pattern</code> and <code>regexp</code> parsers can be used for log lines with an unusual structure. The <code>pattern</code> parser is easier and faster to write; it also outperforms the <code>regexp</code> parser. Multiple parsers can be used by a single log pipeline. This is useful for parsing complex logs. There are examples in <b style="color: #3C92F1" @click="jumpClick('#multiple-parsers')" class="log-link">Multiple parsers</b>.</p>
2021-08-02 19:51:53 +08:00
</div>
</div>
<!-- JSON -->
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content">
<h2 id="JSON">JSON</h2>
<div class="introduce-view__content-label">
<p>The <b>json</b> parser operates in two modes:</p>
<p>1. <b>without</b> parameters:</p>
<div class="logfmt-module" style="padding-left: 20px;">
<p>Adding <code>| json</code> to your pipeline will extract all json properties as labels if the log line is a valid json document. Nested properties are flattened into label keys using the <code>_</code> separator.</p>
<p>Note: <b>Arrays are skipped</b>.</p>
<p>For example the json parsers will extract from the following document:</p>
<pre>{
"protocol": "HTTP/2.0",
"servers": ["129.0.1.1","10.2.1.3"],
"request": {
"time": "6.032",
"method": "GET",
"host": "foo.grafana.net",
"size": "55",
"headers": {
"Accept": "*/*",
"User-Agent": "curl/7.68.0"
}
},
"response": {
"status": 401,
"size": "228",
"latency_seconds": "6.031"
}
}</pre>
<p>The following list of labels:</p>
<pre>"protocol" => "HTTP/2.0"
"request_time" => "6.032"
"request_method" => "GET"
"request_host" => "foo.grafana.net"
"request_size" => "55"
"response_status" => "401"
"response_size" => "228"
"response_latency_seconds" => "6.031"</pre>
</div>
<p>2. <b>with </b>parameters:</p>
<div class="logfmt-module" style="padding-left: 20px;">
<p>Using <code>| json label="expression", another="expression"</code> in your pipeline will extract only the specified json fields to labels. You can specify one or more expressions in this way, the same as <code>label_format</code>; all expressions must be quoted.</p>
<p>Currently, we only support field access (<code>my.field</code>, <code>my["field"]</code>) and array access (<code>list[0]</code>), and any combination of these in any level of nesting (<code>my.list[0]["field"]</code>).</p>
<p>For example, <code>| json first_server="servers[0]", ua="request.headers[\"User-Agent\"]</code> will extract from the following document:</p>
<pre>{
"protocol": "HTTP/2.0",
"servers": ["129.0.1.1","10.2.1.3"],
"request": {
"time": "6.032",
"method": "GET",
"host": "foo.grafana.net",
"size": "55",
"headers": {
"Accept": "*/*",
"User-Agent": "curl/7.68.0"
}
},
"response": {
"status": 401,
"size": "228",
"latency_seconds": "6.031"
}
}
</pre>
<p>The following list of labels:</p>
<pre>"first_server" => "129.0.1.1"
"ua" => "curl/7.68.0"</pre>
<p>If an array or an object returned by an expression, it will be assigned to the label in json format.</p>
<p>For example, <code>| json server_list="servers", headers="request.headers"</code> will extract:</p>
<pre>"server_list" => `["129.0.1.1","10.2.1.3"]`
"headers" => `{"Accept": "*/*", "User-Agent": "curl/7.68.0"}`</pre>
<p>If the label to be extracted is same as the original JSON field, expression can be written as just <code>| json &lt;label&gt;</code></p>
<p>For example, to extract <code>servers</code> fields as label, expression can be written as following</p>
<p><code>| json servers</code> will extract:</p>
<pre>"servers" => `["129.0.1.1","10.2.1.3"]`</pre>
<p>Note that <code>| json servers</code> is same as <code>| json servers="servers"</code></p>
</div>
</div>
</div>
<!-- logfmt -->
<div class="introduce-view__content">
<h2 id="logfmt">logfmt</h2>
<div class="introduce-view__content-label logfmt-module">
<p>The <b>logfmt</b> parser can be added using the <code>| logfmt</code> and will extract all keys and values from the logfmt formatted log line.</p>
<p>For example the following log line:</p>
<pre>at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200</pre>
<p>will get those labels extracted:</p>
<pre>"at" => "info"
"method" => "GET"
"path" => "/"
"host" => "grafana.net"
"fwd" => "124.133.124.161"
"service" => "8ms"
"status" => "200"</pre>
</div>
</div>
<!-- Pattern -->
<div class="introduce-view__content">
<h2 id="pattern">Pattern</h2>
<div class="introduce-view__content-label logfmt-module">
<p>The pattern parser allows the explicit extraction of fields from log lines by defining a pattern expression (<code>| pattern "&lt;pattern-expression&gt;"</code>). The expression matches the structure of a log line.</p>
<p>Consider this NGINX log line.</p>
<p><code class="fillbox">0.191.12.2 - - [10/Jun/2021:09:14:29 +0000] "GET /api/plugins/versioncheck HTTP/1.1" 200 2 "-" "Go-http-client/2.0" "13.76.247.102, 34.120.177.193" "TLSv1.2" "US" ""
</code></p>
<p>This log line can be parsed with the expression</p>
<p><code>&lt;ip&gt; - - &lt;_&gt; "&lt;method&gt; &lt;uri&gt; &lt;_&gt;" &lt;status&gt; &lt;size&gt; &lt;_&gt; "&lt;agent&gt;" &lt;_&gt;</code></p>
<p>to extract these fields:</p>
<pre>"ip" => "0.191.12.2"
"method" => "GET"
"uri" => "/api/plugins/versioncheck"
"status" => "200"
"size" => "2"
"agent" => "Go-http-client/2.0"</pre>
<p>A pattern expression is composed of captures and literals.</p>
<p>A capture is a field name delimited by the <code>&lt;</code> and <code>&gt;</code> characters. <code>&lt;example&gt;</code> defines the field name <code>example</code>. An unnamed capture appears as <code>&lt;_&gt;</code>. <br/>The unnamed capture skips matched content.</p>
<p>Captures are matched from the line beginning or the previous set of literals, to the line end or the next set of literals. If a capture is not matched, the pattern parser will stop.</p>
<p>Literals can be any sequence of UTF-8 characters, including whitespace characters.</p>
<p>By default, a pattern expression is anchored at the start of the log line. If the expression starts with literals, then the log line must also start with the same set of literals. Use <code>&lt;_&gt;</code> at the beginning of the expression if you dont want to anchor the expression at the start.</p>
<p>Consider the log line</p>
<pre>level=debug ts=2021-06-10T09:24:13.472094048Z caller=logging.go:66 traceID=0568b66ad2d9294c msg="POST /loki/api/v1/push (204) 16.652862ms"</pre>
<p>To match <code>msg="</code>, use the expression:</p>
<pre>&lt;_&gt; msg="&lt;method&gt; &lt;path&gt; (&lt;status&gt;) &lt;latency&gt;"</pre>
<p>A pattern expression is invalid if</p>
<ul>
<li>It does not contain any named capture.</li>
<li>It contains two consecutive captures not separated by whitespace characters.</li>
</ul>
</div>
</div>
<!-- Regular expression -->
<div class="introduce-view__content">
<h2 id="regexp">Regular expression</h2>
<div class="introduce-view__content-label logfmt-module">
<p>Unlike the logfmt and json, which extract implicitly all values and takes no parameters, the regexp parser takes a single parameter <code>| regexp "&lt;re&gt;"</code> which is the regular expression using the Golang RE2 syntax.</p>
<p>The regular expression must contain a least one named sub-match (e.g <code>(?P&lt;name&gt;re)</code>), each sub-match will extract a different label.</p>
<p>For example the parser | regexp "(?P&lt;method&gt;\\w+) (?P&lt;path&gt;[\\w|/]+) \\((?P&lt;status&gt;\\d+?)\\) (?P&lt;duration&gt;.*)" will extract from the following line:</p>
<pre>POST /api/prom/api/v1/query_range (200) 1.5s</pre>
<p>those labels:</p>
<pre>"method" => "POST"
"path" => "/api/prom/api/v1/query_range"
"status" => "200"
"duration" => "1.5s"</pre>
</div>
</div>
<!-- unpack -->
<div class="introduce-view__content">
<h2 id="unpack">unpack</h2>
<div class="introduce-view__content-label logfmt-module">
<p>The <code>unpack</code> parser parses a JSON log line, unpacking all embedded labels from Promtails <code>pack</code> stage. <b>A special property <code>_entry</code> will also be used to replace the original log line</b>.</p>
<p>For example, using <code>| unpack</code> with the log line:</p>
<pre>{
"container": "myapp",
"pod": "pod-3223f",
"_entry": "original log message"
}</pre>
<p>extracts the <code>container</code> and <code>pod</code> labels; it sets <code>original log message</code> as the new log line.</p>
<p>You can combine the <code>unpack</code> and <code>json</code> parsers (or any other parsers) if the original embedded log line is of a specific format.</p>
</div>
</div>
<!-- Line format expression -->
<div class="introduce-view__content">
<h2 id="line-format-expressions">Line format expression</h2>
<div class="introduce-view__content-label logfmt-module">
<p v-pre>The line format expression can rewrite the log line content by using the text/template format. It takes a single string parameter <code>| line_format "{{.label_name}}"</code>, which is the template format. All labels are injected variables into the template and are available to use with the <code>{{.label_name}}</code> notation.</p>
<p>For example the following expression:</p>
<pre v-pre>{container="frontend"} | logfmt | line_format "{{.query}} {{.duration}}"</pre>
<p>Will extract and rewrite the log line to only contains the query and the duration of a request.</p>
<p v-pre>You can use double quoted string for the template or backticks <code>`{{.label_name}}`</code> to avoid the need to escape special characters.</p>
<p><code>line_format</code> also supports <code>math</code> functions. Example:</p>
<p>If we have the following labels <code>ip=1.1.1.1</code>, <code>status=200</code> and <code>duration=3000(ms)</code>, we can divide the duration by <code>1000</code> to get the value in seconds.</p>
<pre v-pre>{container="frontend"} | logfmt | line_format "{{.ip}} {{.status}} {{div .duration 1000}}"</pre>
<p>The above query will give us the <code>line</code> as <code>1.1.1.1 200 3</code></p>
<p>See template functions to learn about available functions in the template format.</p>
</div>
</div>
<!-- Labels format expression -->
<div class="introduce-view__content">
<h2 id="label-format-expressions">Labels format expression</h2>
2021-08-02 19:51:53 +08:00
<div class="introduce-view__content-label">
<p>The <code>| label_format</code> expression can rename, modify or add labels. It takes as parameter a comma separated list of equality operations, enabling multiple operations at once.</p>
<p>When both side are label identifiers, for example <code>dst=src</code>, the operation will rename the <code>src</code> label into <code>dst</code>.</p>
<p>The right side can alternatively be a template string (double quoted or backtick), for example dst="<code v-pre>{{.status}} {{.query}}</code>", in which case the <code>dst</code> label value is replaced by the result of the text/template evaluation. This is the same template engine as the <code>| line_format</code> expression, which means labels are available as variables and you can use the same list of functions.</p>
<p>In both cases, if the destination label doesnt exist, then a new one is created.</p>
<p>The renaming form <code>dst=src</code> will drop the <code>src</code> label after remapping it to the <code>dst</code> label. However, the template form will preserve the referenced labels, such that <code v-pre>dst="{{.src}}"</code> results in both <code>dst</code> and <code>src</code> having the same value.</p>
<pre>A single label name can only appear once per expression. This means <code>| label_format foo=bar,foo="new"</code> is not allowed but you can use two expressions for the desired effect: <code>| label_format foo=bar | label_format foo="new"</code></pre>
</div>
</div>
<!-- Log queries examples -->
2022-08-31 16:16:21 +08:00
<h1 class="page-header-two" id="log-queries-examples">Log queries examples</h1>
<!-- Multiple filtering -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="multiple-filtering">Multiple filtering</h2>
<div class="introduce-view__content-label">
<p>Filtering should be done first using label matchers, then line filters (when possible) and finally using label filters. The following query demonstrate this.</p>
<p><code v-pre class="fillbox">{cluster="ops-tools1", namespace="loki-dev", job="loki-dev/query-frontend"} |= "metrics.go" !="out of order" | logfmt | duration > 30s or status_code!="200"</code></p>
</div>
</div>
<!-- Multiple parsers -->
<div class="introduce-view__content">
<h2 id="multiple-parsers">Multiple parsers</h2>
<div class="introduce-view__content-label">
<p>To extract the method and the path of the following logfmt log line:</p>
<p><code v-pre class="fillbox">level=debug ts=2020-10-02T10:10:42.092268913Z caller=logging.go:66 traceID=a9d4d8a928d8db1 msg="POST /api/prom/api/v1/query_range (200) 1.5s"</code></p>
<p>You can use multiple parsers (logfmt and regexp) like this.</p>
<p><code v-pre class="fillbox">{job="loki-ops/query-frontend"} | logfmt | line_format "{{.msg}}" | regexp "(?P&lt;method&gt;\\w+) (?P&lt;path&gt;[\\w|/]+) \\((?P&lt;status&gt;\\d+?)\\) (?P&lt;duration&gt;.*)"</code></p>
<p>This is possible because the <code>| line_format</code> reformats the log line to become <code>POST /api/prom/api/v1/query_range (200) 1.5s</code> which can then be parsed with the <code>| regexp ...</code> parser.</p>
</div>
</div>
<!-- Formatting -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="formatting">Formatting</h2>
<div class="introduce-view__content-label logfmt-module">
<p>The following query shows how you can reformat a log line to make it easier to read on screen.</p>
<pre v-pre>{cluster="ops-tools1", name="querier", namespace="loki-dev"}
|= "metrics.go" != "loki-canary"
| logfmt
| query != ""
2022-08-31 16:16:21 +08:00
| label_format query="{{ Replace .query &#92;"&#92;&#92;n&#92;" &#92;"&#92;" -1 }}"
| line_format "{{ .ts}}&#92;t{{.duration}}&#92;ttraceID = {{.traceID}}&#92;t{{ printf &#92;"%-100.100s&#92;" .query }} "</pre>
<p>Label formatting is used to sanitize the query while the line format reduce the amount of information and creates a tabular output.</p>
<p>For these given log lines:</p>
<pre>level=info ts=2020-10-23T20:32:18.094668233Z caller=metrics.go:81 org_id=29 traceID=1980d41501b57b68 latency=fast query="{cluster=\"ops-tools1\", job=\"loki-ops/query-frontend\"} |= \"query_range\"" query_type=filter range_type=range length=15m0s step=7s duration=650.22401ms status=200 throughput_mb=1.529717 total_bytes_mb=0.994659
level=info ts=2020-10-23T20:32:18.068866235Z caller=metrics.go:81 org_id=29 traceID=1980d41501b57b68 latency=fast query="{cluster=\"ops-tools1\", job=\"loki-ops/query-frontend\"} |= \"query_range\"" query_type=filter range_type=range length=15m0s step=7s duration=624.008132ms status=200 throughput_mb=0.693449 total_bytes_mb=0.432718</pre>
<p>The result would be:</p>
<pre>2020-10-23T20:32:18.094668233Z 650.22401ms traceID = 1980d41501b57b68 {cluster="ops-tools1", job="loki-ops/query-frontend"} |= "query_range"
2020-10-23T20:32:18.068866235Z 624.008132ms traceID = 1980d41501b57b68 {cluster="ops-tools1", job="loki-ops/query-frontend"} |= "query_range"</pre>
</div>
</div>
<!-- Metric queries -->
<div class="introduce-view__content">
2022-09-01 11:06:16 +08:00
<h1 class="page-header-one" id="metric-queries">Metric queries</h1>
<div class="introduce-view__content-label">
<p>Metric queries extend log queries by applying a function to log query results. This powerful feature creates metrics from logs.</p>
<p>Metric queries can be used to calculate the rate of error messages or the top N log sources with the greatest quantity of logs over the last 3 hours.</p>
<p>Combined with parsers, metric queries can also be used to calculate metrics from a sample value within the log line, such as latency or request size. All labels, including extracted ones, will be available for aggregations and generation of new series.</p>
</div>
</div>
<!-- Range Vector aggregation -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h1 class="page-header-two" id="range-vector-aggregation">Range Vector aggregation</h1>
<div class="introduce-view__content-label">
<p>LogQL shares the range vector concept of Prometheus. In Grafana Loki, the selected range of samples is a range of selected log or label values.</p>
<p>The aggregation is applied over a time duration. Loki defines Time Durations with the same syntax as Prometheus.</p>
<p>Loki supports two types of range vector aggregations: log range aggregations and unwrapped range aggregations.</p>
</div>
</div>
<!-- Log range aggregations -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="log-range-aggregations">Log range aggregations</h2>
<div class="introduce-view__content-label">
<p>A log range aggregation is a query followed by a duration. A function is applied to aggregate the query over the duration. The duration can be placed after the log stream selector or at end of the log pipeline.</p>
<p>The functions:</p>
<ul>
<li><code> rate(log-range)</code>: calculates the number of entries per second</li>
<li><code> count_over_time(log-range)</code>: counts the entries for each log stream within the given range.</li>
<li><code> bytes_rate(log-range)</code>: calculates the number of bytes per second for each stream.</li>
<li><code> bytes_over_time(log-range)</code>: counts the amount of bytes used by each log stream for a given range.</li>
<li><code> absent_over_time(log-range)</code>: returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements. (<code>absent_over_time</code> is useful for alerting on when no time series and logs stream exist for label combination for a certain amount of time.)</li>
</ul>
<p>Examples:</p>
<ul>
<li>
<p>Count all the log lines within the last five minutes for the MySQL job.</p>
<p><code class="fillbox">count_over_time({job="mysql"}[5m])</code></p>
</li>
<li>
<p>This aggregation includes filters and parsers. It returns the per-second rate of all non-timeout errors within the last minutes per host for the MySQL job and only includes errors whose duration is above ten seconds.</p>
<p><code class="fillbox">sum by (host) (rate({job="mysql"} |= "error" != "timeout" | json | duration > 10s [1m]))</code></p>
</li>
</ul>
</div>
</div>
<!-- Unwrapped range aggregations -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="unwrapped-range-aggregations">Unwrapped range aggregations</h2>
<div class="introduce-view__content-label">
<p>Unwrapped ranges uses extracted labels as sample values instead of log lines. However to select which label will be used within the aggregation, the log query must end with an unwrap expression and optionally a label filter expression to discard errors.</p>
<p>The unwrap expression is noted <code>| unwrap label_identifier</code> where the label identifier is the label name to use for extracting sample values.</p>
<p>Since label values are string, by default a conversion into a float (64bits) will be attempted, in case of failure the <code>__error__</code> label is added to the sample. Optionally the label identifier can be wrapped by a conversion function <code>| unwrap &lt;function&gt;(label_identifier)</code>, which will attempt to convert the label value from a specific format.</p>
<p>We currently support the functions:</p>
<ul>
<li><code>duration_seconds(label_identifier)</code> (or its short equivalent <code>duration</code>) which will convert the label value in seconds from the go duration format (e.g <code>5m</code>, <code>24s30ms</code>).</li>
<li><code>bytes(label_identifier)</code> which will convert the label value to raw bytes applying the bytes unit (e.g. <code>5 MiB</code>, <code>3k</code>, <code>1G</code>).</li>
</ul>
<p>Supported function for operating over unwrapped ranges are:</p>
<ul>
<li><code> rate(unwrapped-range)</code>: calculates per second rate of the sum of all values in the specified interval.</li>
<li><code> rate_counter(unwrapped-range)</code>: calculates per second rate of the values in the specified interval and treating them as counter metric</li>
<li><code> sum_over_time(unwrapped-range)</code>: the sum of all values in the specified interval.</li>
<li><code> avg_over_time(unwrapped-range)</code>: the average value of all points in the specified interval.</li>
<li><code> max_over_time(unwrapped-range)</code>: the maximum value of all points in the specified interval.</li>
<li><code> min_over_time(unwrapped-range)</code>: the minimum value of all points in the specified interval</li>
<li><code> first_over_time(unwrapped-range)</code>: the first value of all points in the specified interval</li>
<li><code> last_over_time(unwrapped-range)</code>: the last value of all points in the specified interval</li>
<li><code> stdvar_over_time(unwrapped-range)</code>: the population standard variance of the values in the specified interval.</li>
<li><code> stddev_over_time(unwrapped-range)</code>: the population standard deviation of the values in the specified interval.</li>
<li><code> quantile_over_time(scalar,unwrapped-range)</code>: the φ-quantile (0 φ 1) of the values in the specified interval.</li>
<li><code> absent_over_time(unwrapped-range)</code>: returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements. ( <code>absent_over_time</code> is useful for alerting on when no time series and logs stream exist for label combination for a certain amount of time.)</li>
</ul>
<p>Except for <code>sum_over_time</code>,<code>absent_over_time</code> and <code>rate</code>, unwrapped range aggregations support grouping.</p>
<p><code class="fillbox">&lt;aggr-op&gt;([parameter,] &lt;unwrapped-range&gt;) [without|by (&lt;label list&gt;)]</code></p>
<p>Which can be used to aggregate over distinct labels dimensions by including a <code>without</code> or <code>by</code> clause.</p>
<p><code>without</code> removes the listed labels from the result vector, while all other labels are preserved the output. <code>by</code> does the opposite and drops labels that are not listed in the <code>by</code> clause, even if their label values are identical between all elements of the vector.</p>
</div>
</div>
<!-- Unwrapped examples -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="unwrapped-examples">Unwrapped examples</h2>
<div class="introduce-view__content-label logfmt-module">
<pre>quantile_over_time(0.99,
{cluster="ops-tools1",container="ingress-nginx"}
| json
| __error__ = ""
| unwrap request_time [1m]) by (path)</pre>
<p>This example calculates the p99 of the nginx-ingress latency by path.</p>
<pre>sum by (org_id) (
sum_over_time(
{cluster="ops-tools1",container="loki-dev"}
|= "metrics.go"
| logfmt
| unwrap bytes_processed [1m])
)</pre>
<p>This calculates the amount of bytes processed per organization ID.</p>
</div>
</div>
<!-- Built-in aggregation operators -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h1 class="page-header-two" id="built-in-aggregation-operators">Built-in aggregation operators</h1>
<div class="introduce-view__content-label">
<p>Like PromQL, LogQL supports a subset of built-in aggregation operators that can be used to aggregate the element of a single vector, resulting in a new vector of fewer elements but with aggregated values:</p>
<ul>
<li><code> sum</code>: Calculate sum over labels</li>
<li><code> avg</code>: Calculate the average over labels</li>
<li><code> min</code>: Select minimum over labels</li>
<li><code> max</code>: Select maximum over labels</li>
<li><code> stddev</code>: Calculate the population standard deviation over labels</li>
<li><code> stdvar</code>: Calculate the population standard variance over labels</li>
<li><code> count</code>: Count number of elements in the vector</li>
<li><code> topk</code>: Select largest k elements by sample value</li>
<li><code> bottomk</code>: Select smallest k elements by sample value</li>
</ul>
<p>The aggregation operators can either be used to aggregate over all label values or a set of distinct label values by including a <code>without</code> or a <code>by</code> clause:</p>
<p><code class="fillbox">&lt;aggr-op&gt;([parameter,] &lt;vector expression&gt;) [without|by (&lt;label list&gt;)]</code></p>
<p><code>parameter</code> is required when using <code>topk</code> and <code>bottomk</code>. <code>topk</code> and <code>bottomk</code> are different from other aggregators in that a subset of the input samples, including the original labels, are returned in the result vector.</p>
<p><code>by</code> and <code>without</code> are only used to group the input vector. The <code>without</code> clause removes the listed labels from the resulting vector, keeping all others. The <code>by</code> clause does the opposite, dropping labels that are not listed in the clause, even if their label values are identical between all elements of the vector.</p>
</div>
</div>
<!-- Vector aggregation examples -->
<div class="introduce-view__content">
2022-08-31 16:16:21 +08:00
<h2 id="vector-aggregation-examples">Vector aggregation examples</h2>
<div class="introduce-view__content-label">
<p>Get the top 10 applications by the highest log throughput:</p>
<p><code class="fillbox">topk(10,sum(rate({region="us-east1"}[5m])) by (name))</code></p>
<p>Get the count of log lines for the last five minutes for a specified job, grouping by level:</p>
<p><code class="fillbox">sum(count_over_time({job="mysql"}[5m])) by (level)</code></p>
<p>Get the rate of HTTP GET requests to the <code>/home</code> endpoint for NGINX logs by region:</p>
<p><code class="fillbox">avg(rate(({job="nginx"} |= "GET" | json | path="/home")[10s])) by (region)</code></p>
2021-08-02 19:51:53 +08:00
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<transition name="right-box">
<chartRightBox v-if="rightBox.show" ref="addChartModal" :chart="chartData" :from="$CONSTANTS.fromRoute.explore" :panel-data="panelData" :show-panel="{id: -1, name: '', type: 'explore'}" @close="handleBox(false)" @on-create-success="createSuccess"></chartRightBox>
2021-08-02 19:51:53 +08:00
</transition>
</div>
</template>
<script>
import bus from '../../../../libs/bus'
import promqlInput from './promqlInput'
import chart from '../overview/chart'
import axios from 'axios'
import { getUUID } from '../../../common/js/common'
2022-04-12 10:30:33 +08:00
import chartDataFormat from '../../../chart/chartDataFormat'
2021-08-02 19:51:53 +08:00
import logTab from './logTab'
import promqlInputMixin from '@/components/common/mixin/promqlInput'
import chartRightBox from '@/components/common/rightBox/chart/chartRightBox'
2021-08-02 19:51:53 +08:00
export default {
name: 'exploreItem',
components: {
'promql-input': promqlInput,
chartRightBox,
2021-08-02 19:51:53 +08:00
chart,
logTab
},
props: {
tabIndex: Number,
closable: Boolean
},
mixins: [promqlInputMixin],
2021-08-02 19:51:53 +08:00
data () {
return {
chartLoading: false,
logTabNoData: false,
2021-08-02 19:51:53 +08:00
rightBox: { // 面板弹出框相关
show: false
},
value: true,
tabPosition: 'none',
tableId: 'explore',
2021-08-02 19:51:53 +08:00
searchMetrics: [
{
value: 'Metrics',
2022-06-21 18:14:21 +08:00
label: this.$t('overall.metric'),
icon: 'nz-icon nz-icon-Metrics'
2021-08-02 19:51:53 +08:00
},
{
value: 'Logs',
2022-07-12 14:06:14 +08:00
label: this.$t('dashboard.panel.chartForm.typeVal.log.label'),
icon: 'nz-icon nz-icon-logs'
2021-08-02 19:51:53 +08:00
}
],
2022-06-21 18:14:21 +08:00
selectValue: this.$t('overall.metric'),
2021-08-25 13:57:04 +08:00
selectIcon: 'nz-icon nz-icon-Metrics',
2021-08-02 19:51:53 +08:00
showMetrics: true,
promqlCount: 1,
promqlKeys: [],
expressions: [''],
filterTime: [
bus.timeFormate(bus.getOffsetTimezoneData(-1)),
bus.timeFormate(bus.getOffsetTimezoneData())
2021-08-02 19:51:53 +08:00
],
/* 工具参数 */
tools: {
loading: false, // 是否显示table加载动画
showCustomTableTitle: false, // 自定义列弹框是否显示
customTableTitle: [] // 自定义列工具的数据
},
tableTitle: [],
showIntroduce: true,
defaultChartVisible: true,
defaultTableVisible: true,
chartVisible: true,
tableVisible: true,
pageObj: {
pageNo: 1,
pageSize: this.$CONSTANTS.defaultPageSize,
total: 0
},
tableData: [],
saveDisabled: true,
panelData: [],
chartUnit: 0,
historyParam: { useHistory: true, key: 'expore-history' },
chartData: {},
2021-08-02 19:51:53 +08:00
collapseValue: ['1', '2'],
showTab: ['1', '2'],
logData: [],
letter: [
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
]
2021-08-02 19:51:53 +08:00
}
},
created () {
this.getPanelData()
this.promqlKeys.push({
id: getUUID(),
state: 1
})
this.selectMetricsLogs()
2021-08-02 19:51:53 +08:00
},
methods: {
jumpClick (id) {
document.querySelector(id).scrollIntoView(true)
},
2021-08-25 13:57:04 +08:00
selectMetricsLogs (val, icon, label) {
if (val) {
this.selectIcon = icon
this.selectValue = val
} else {
2021-08-25 13:57:04 +08:00
label = 'Metrics'
}
2021-08-25 13:57:04 +08:00
this.changeType(label)
},
2021-08-02 19:51:53 +08:00
changeType (value) {
this.showMetrics = value === 'Metrics'
this.showIntroduce = true
this.resetExpression()
},
split () {
this.$emit('split', this.tabIndex)
},
pageNo (val) {
this.pageObj.pageNo = val
this.tableData = this.filterShowData(this.storedTableData, this.pageObj)
},
pageSize (val) {
this.pageObj.pageSize = val
this.tableData = this.filterShowData(this.storedTableData, this.pageObj)
},
filterShowData (source, pageObj) {
if (source) return source.slice((pageObj.pageNo - 1) * pageObj.pageSize, pageObj.pageNo * pageObj.pageSize)
2021-08-02 19:51:53 +08:00
},
chartUnitChange (unit) {
this.chartUnit = unit
this.$nextTick(() => {
this.expressionChange()
})
},
exportLog ({ limit, descending }) {
const params = {
logql: this.expressions,
start: this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[0])),
end: this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[1])),
2021-08-02 19:51:53 +08:00
direction: descending ? 'backward' : 'forward',
limit
}
axios.get('/logs/loki/export', { responseType: 'blob', params: params }).then(res => {
if (window.navigator.msSaveOrOpenBlob) {
// 兼容ie11
const blobObject = new Blob([res.data])
window.navigator.msSaveOrOpenBlob(blobObject, 'log')
} else {
const url = URL.createObjectURL(new Blob([res.data]))
const a = document.createElement('a')
document.body.appendChild(a) // 此处增加了将创建的添加到body当中
a.href = url
a.download = 'log'
a.target = '_blank'
a.click()
a.remove() // 将a标签移除
}
}, error => {
const $self = this
const reader = new FileReader()
reader.onload = function (event) {
const responseText = reader.result
const exception = JSON.parse(responseText)
if (exception.message) {
$self.$message.error(exception.message)
} else {
console.error(error)
}
}
reader.readAsText(error.response.data)
})
},
queryLogData (limit) { // log的chart和table是一个请求
this.chartLoading = true
if (!limit) {
limit = this.$refs.logDetail ? this.$refs.logDetail.getLimit() : 100
}
this.$refs.logDetail && this.$refs.logDetail.resetOperation()
2021-08-02 19:51:53 +08:00
if (this.expressions.length > 0) {
const requestArr = []
// 过滤掉state为0的元素
2021-08-02 19:51:53 +08:00
this.expressions.forEach((item, index) => {
if (item != '' && this.promqlKeys[index].state) {
requestArr.push(this.$get('/logs/loki/api/v1/query_range?format=1&query=' + encodeURIComponent(item) + '&start=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[0])) + '&end=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[1])) + '&limit=' + limit))
2021-08-02 19:51:53 +08:00
}
})
if (requestArr.length > 0) {
this.showIntroduce = false
this.saveDisabled = false
}
axios.all(requestArr).then(res => {
this.chartLoading = false
2021-08-21 17:34:01 +08:00
const errorRowIndex = []
res.forEach((r, i) => {
if (typeof r === 'string') {
errorRowIndex.push(i)
}
2021-08-21 17:34:01 +08:00
})
if (errorRowIndex.length > 0) {
this.$message.error(this.$t('tip.errorInRow') + ': ' + errorRowIndex.map(e => e + 1).join(' ,'))
res = res.filter((r, i) => errorRowIndex.indexOf(i) === -1)
}
2021-08-21 17:34:01 +08:00
if (res.length > 0) {
const logData = res.map(r => r.data)
if (logData[0].result.length > 0) {
this.logTabNoData = false
} else {
this.logTabNoData = true
}
const hasGraph = logData.some(d => d.resultType === 'matrix')
const hasLog = logData.some(d => d.resultType === 'streamsFormat')
2021-08-21 17:34:01 +08:00
const graphTabIndex = this.showTab.indexOf('1')
if (hasGraph) {
if (graphTabIndex === -1) {
this.showTab.push('1')
}
} else {
if (graphTabIndex > -1) {
this.showTab.splice(graphTabIndex, 1)
}
}
2021-08-21 17:34:01 +08:00
const logTabIndex = this.showTab.indexOf('2')
if (hasLog) {
if (logTabIndex === -1) {
this.showTab.push('2')
2021-08-21 17:34:01 +08:00
}
} else {
if (logTabIndex > -1) {
this.showTab.splice(logTabIndex, 1)
}
}
2021-08-21 17:34:01 +08:00
this.$nextTick(() => {
this.logData = logData
hasGraph && this.loadLogGraph()
2021-08-21 17:34:01 +08:00
})
}
2021-08-21 17:34:01 +08:00
}).catch(e => {
this.chartLoading = false
2021-08-21 17:34:01 +08:00
this.$message.error(this.$t('terminallog.statusItem.unknownError'))
2021-08-02 19:51:53 +08:00
})
}
},
loadLogGraph () {
const graphData = this.logData.filter(l => l.resultType === 'matrix')
if (graphData && graphData.length > 0) {
this.$refs.logChart.startLoading()
const promqlInputIndexs = []
const queryExpression = []
2021-09-03 16:30:55 +08:00
let series = []
2021-08-02 19:51:53 +08:00
const legend = []
this.expressions.forEach((item, index) => {
if (item !== '') {
promqlInputIndexs.push(index)
queryExpression.push(item)
}
})
this.logData.forEach((response, index) => {
if (response.resultType === 'matrix') {
const promqlIndex = promqlInputIndexs[index]
const data = response.result
if (!data || data.length < 1) {
return
}
data.forEach((result, i) => {
const seriesItem = {
name: '',
symbol: 'emptyCircle', // 去掉点
symbolSize: [2, 2],
showSymbol: false,
smooth: 0.2, // 曲线变平滑
data: [],
lineStyle: {
width: 1,
opacity: 0.9
},
type: 'line'
2021-08-02 19:51:53 +08:00
}
let legendName = ''
seriesItem.data = result.values.map((item) => {
return [item[0] * 1000, item[1]]
2021-08-02 19:51:53 +08:00
})
if (result.metric && Object.keys(result.metric).length > 0) {
const metric = Object.assign({}, result.metric)
seriesItem.name += metric.__name__ ? metric.__name__ : ''
seriesItem.name += '{'
delete metric.__name__
for (const key in metric) {
seriesItem.name += key + '=' + '"' + metric[key] + '",'
}
legendName = seriesItem.name.substr(0, seriesItem.name.length - 1)
legendName += '}'
} else {
legendName = queryExpression[index]
}
seriesItem.name = legendName + '-' + index
series.push(seriesItem)
legend.push({ name: seriesItem.name, alias: legendName, isGray: false })
})
2021-08-02 19:51:53 +08:00
this.$refs['promql-' + promqlIndex][0].setError('')
}
2021-08-02 19:51:53 +08:00
})
this.defaultChartVisible = true
this.$nextTick(() => {
this.$refs.logChart.setLegend(legend)
this.$refs.logChart.setRandomColors(series.length)
2021-09-03 16:30:55 +08:00
if (!series.length) {
series = ''
}
this.$refs.logChart.setSeries(series)
2021-08-02 19:51:53 +08:00
this.$refs.logChart.endLoading()
})
}
},
queryChartData () {
this.$refs.exploreChart.startLoading()
if (this.expressions.length > 0) {
const requestArr = []
const promqlInputIndexs = []
const queryExpression = []
// 过滤掉state为0的元素
this.expressions.forEach((item, index) => {
if (item != '' && this.promqlKeys[index].state) {
const step = bus.getStep(bus.formateTimeToTime(this.filterTime[0]), bus.formateTimeToTime(this.filterTime[1]))
promqlInputIndexs.push(index)
queryExpression.push(item)
requestArr.push(this.$get('/prom/api/v1/query_range?query=' + encodeURIComponent(item) + '&start=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[0])) + '&end=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[1])) + '&step=' + step + '&nullType=null'))
2021-08-02 19:51:53 +08:00
}
})
if (requestArr.length > 0) {
this.showIntroduce = false
this.saveDisabled = false
}
axios.all(requestArr).then(res => {
let series = []
const legend = []
if (res.length > 0) {
res.forEach((response, index) => {
const promqlIndex = promqlInputIndexs[index]
if (response.data && response.status == 'success') {
const data = response.data.result
if ((!data || data.length < 1) && response.message) {
this.$refs['promql-' + promqlIndex][0].setError(response.message)
return
}
data.forEach((result, i) => {
const seriesItem = {
name: '',
symbol: 'emptyCircle', // 去掉点
symbolSize: [2, 2],
showSymbol: false,
smooth: 0.2, // 曲线变平滑
data: [],
lineStyle: {
width: 1,
opacity: 0.9
},
type: 'line'
2021-08-02 19:51:53 +08:00
}
let legendName = ''
seriesItem.data = result.values.map((item) => {
return [item[0] * 1000, item[1]]
2021-08-02 19:51:53 +08:00
})
if (result.metric && Object.keys(result.metric).length > 0) {
const metric = Object.assign({}, result.metric)
seriesItem.name += metric.__name__ ? metric.__name__ : ''
seriesItem.name += '{'
delete metric.__name__
for (const key in metric) {
seriesItem.name += key + '=' + '"' + metric[key] + '",'
}
legendName = seriesItem.name.substr(0, seriesItem.name.length - 1)
legendName += '}'
} else {
legendName = queryExpression[index]
}
seriesItem.name = legendName + '-' + index
series.push(seriesItem)
legend.push({ name: seriesItem.name, alias: legendName, isGray: false })
})
2021-08-02 19:51:53 +08:00
this.$refs['promql-' + promqlIndex][0].setError('')
} else {
if (response.error) {
this.$refs['promql-' + promqlIndex][0].setError(response.error)
} else {
this.$refs['promql-' + promqlIndex][0].setError(response)
}
2021-09-03 16:30:55 +08:00
}
})
this.$refs.exploreChart.setLegend(legend)
this.$refs.exploreChart.setRandomColors(series.length)
if (!series.length) {
series = ''
2021-08-02 19:51:53 +08:00
}
this.$refs.exploreChart.setSeries(series)
this.defaultChartVisible = true
}
this.$refs.exploreChart.endLoading()
})
}
2021-08-02 19:51:53 +08:00
},
queryTableData () {
this.tools.loading = true
if (this.expressions.length > 0) {
const requestArr = []
this.expressions.forEach((item, index) => {
// 过滤掉state为0的元素
if (item !== '' && this.promqlKeys[index].state) {
requestArr.push(this.$get('/prom/api/v1/query?query=' + encodeURIComponent(item)))
2021-08-02 19:51:53 +08:00
}
})
if (requestArr.length > 0) {
this.showIntroduce = false
}
axios.all(requestArr).then(res => {
const tData = []
let tLabels = []
if (res.length > 0) {
this.tableData = []
this.tableTitle = []
res.forEach((response, index) => {
if (response.data && response.status === 'success') {
const data = response.data.result
if (data) {
data.forEach((result, i) => {
const metrics = Object.assign({}, result.metric)
if (!Array.isArray(result.value)) {
const val = result
result = {
value: ['', val]
2021-08-02 19:51:53 +08:00
}
}
this.$set(metrics, 'value#' + index, chartDataFormat.getUnit(this.chartUnit || 2).compute(result.value[1], null, 2))
2021-12-27 17:59:57 +08:00
this.$set(metrics, 'time', bus.timeFormate(bus.computeTimezone(result.value[0] * 1000)))
for (const key in metrics) {
const label = {
label: key,
prop: key,
show: true
2021-08-02 19:51:53 +08:00
}
const temp = tLabels.find((item, index) => {
return item.prop == label.prop
})
if (!temp) {
tLabels.push(label)
}
}
tData.push(metrics)
})
2021-08-02 19:51:53 +08:00
}
}
tLabels.sort((a, b) => {
return a.prop.charCodeAt(0) - b.prop.charCodeAt(0)
})
tLabels = tLabels.filter(label => label.prop !== 'time')
tLabels.unshift({
label: this.$t('overall.time'),
prop: 'time',
show: true
})
const filterArr = ['alertname', 'severity_id', 'severity', 'rule_type', 'asset_id', 'endpoint_id', 'project_id', 'datacenter_id', 'module_id', 'nz_agent_id', 'parent_asset_id']
tLabels.forEach(tLabel => {
if (filterArr.indexOf(tLabel.prop) !== -1) {
tLabel.show = false
}
})
})
if (tData.length > 0) {
this.storedTableData = Object.assign([], tData)
this.pageObj.total = this.storedTableData.length
this.tableData = this.filterShowData(this.storedTableData, this.pageObj)
this.tableTitle = Object.assign([], tLabels)
this.tools.customTableTitle = Object.assign([], tLabels)
this.defaultTableVisible = true
} else {
// this.defaultTableVisible = false;
this.pageObj.total = 0
this.pageObj.pageNo = 1
2021-08-02 19:51:53 +08:00
}
}
this.tools.loading = false
})
}
2021-08-02 19:51:53 +08:00
},
expressionChange () {
const nowTimeType = this.$refs.pickTime.$refs.timePicker.nowTimeType
this.pageObj.pageNo = 1
2021-08-02 19:51:53 +08:00
this.setSearchTime(nowTimeType.type, nowTimeType.value)
if (this.showMetrics) {
if (this.expressions && this.expressions.length >= 1) {
this.queryTableData()
this.queryChartData()
this.storeHistory()
}
} else {
if (this.expressions && this.expressions.length >= 1) {
this.queryLogData()
}
}
},
setSearchTime (type, val) { // 设置searchTime
if (type === 'minute') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setMinutes(new Date(bus.computeTimezone(new Date().getTime())).getMinutes() - val))
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())))
2021-08-02 19:51:53 +08:00
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'm')
} else if (type === 'hour') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setHours(new Date(bus.computeTimezone(new Date().getTime())).getHours() - val))
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())))
2021-08-02 19:51:53 +08:00
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'h')
} else if (type === 'date') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setDate(new Date(bus.computeTimezone(new Date().getTime())).getDate() - val))
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())))
2021-08-02 19:51:53 +08:00
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'd')
}
this.$refs.pickTime.$refs.timePicker.searchTime = this.filterTime
},
storeHistory () {
const expire = 24
const historyJson = localStorage.getItem(this.historyParam.key)
// 过滤掉state为0的元素
const expressions = this.expressions.filter((item, index) => {
return item && item != '' && this.promqlKeys[index].state
2021-08-02 19:51:53 +08:00
})
const username = localStorage.getItem('nz-username')
2021-08-02 19:51:53 +08:00
if (historyJson && historyJson != 'undefined' && historyJson != '') {
const historyObj = JSON.parse(historyJson)
let history = historyObj[username]
if (history) {
// 过滤过期表达式
history = history.filter(item => {
return item.time + item.expire >= new Date().getTime()
})
let repeat = history.filter(item => {
return expressions.includes(item.insertText)
})
const old = history.filter(item => {
return !expressions.includes(item.insertText)
})
const freshExpression = expressions.filter(item => {
const find = history.find(t => { return t.insertText == item })
return !find
})
repeat = repeat.map(item => {
item.time = new Date().getTime()
item.num += 1
item.documentation = this.$t('dashboard.metricPreview.historyTip', { time: item.num, hour: 24 })
return item
})
const fresh = freshExpression.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
historyObj[username] = fresh.concat(repeat).concat(old)
} else {
const history = expressions.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
historyObj[username] = history
}
localStorage.setItem(this.historyParam.key, JSON.stringify(historyObj))
} else {
const history = expressions.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
if (history && history.length > 0) {
const stored = {}
stored[username] = history
localStorage.setItem(this.historyParam.key, JSON.stringify(stored))
}
}
},
// 禁用启用表达式
enableExpression (index) {
this.promqlKeys[index].state = this.promqlKeys[index].state ? 0 : 1
const temp = this.expressions.some((item, index) => {
return item != '' && this.promqlKeys[index].state
})
if (!temp) {
this.showIntroduce = true
}
this.expressionChange()
},
2021-08-02 19:51:53 +08:00
addExpression (index) {
this.expressions.splice(index + 1, 0, '')
this.promqlKeys.splice(index + 1, 0, {
id: getUUID(),
state: 1
})
2021-08-02 19:51:53 +08:00
this.promqlCount++
},
copyExpression (index) {
this.expressions.push(this.expressions[index])
this.promqlKeys.push({
id: getUUID(),
state: 1
})
2021-08-02 19:51:53 +08:00
this.promqlCount++
},
removeExpression (index) {
if (this.promqlCount > 1) {
this.expressions.splice(index, 1)
this.promqlKeys.splice(index, 1)
this.promqlCount--
}
},
resetExpression () {
this.expressions = ['']
this.promqlKeys = [{
id: getUUID(),
state: 1
}]
2021-08-02 19:51:53 +08:00
this.promqlCount = 1
},
changeChartVisible () {
this.chartVisible = !this.chartVisible
},
changeTableVisible () {
this.tableVisible = !this.tableVisible
},
handleBox (show) {
this.rightBox.show = show
},
saveChart () {
const chart = {
id: '',
2021-08-02 19:51:53 +08:00
name: '',
panelName: '',
2021-08-02 19:51:53 +08:00
type: 'line',
span: 4,
datasource: 'metrics',
height: 4,
unit: 2,
2021-08-02 19:51:53 +08:00
param: {
stack: 0,
nullType: 'null',
legend: { placement: 'bottom', values: [], show: true },
thresholdShow: true,
thresholds: [{ value: undefined, color: '#eeeeeeff' }]
2021-08-02 19:51:53 +08:00
},
elements: [],
panel: '',
sync: 0,
remark: '',
groupId: -1
2021-08-02 19:51:53 +08:00
}
this.expressions.forEach((exp, index) => {
chart.elements.push({ state: this.promqlKeys[index].state, expression: exp, legend: '', type: 'expert', id: '', name: this.transformNumToLetter(index) })
2021-08-02 19:51:53 +08:00
})
this.chartData = chart
2021-08-02 19:51:53 +08:00
this.rightBox.show = true
},
saveChartLogs () {
const chart = {
id: '',
name: '',
panelName: '',
span: 4,
height: 4,
unit: 2,
groupId: -1,
updateBy: 1,
updateAt: '2022-05-18 07:51:45',
type: 'log',
weight: 6,
param: { limit: 100 },
pid: null,
buildIn: 0,
seq: null,
x: 0,
y: 1.93,
elements: [],
children: null,
chartNums: null,
asset: null,
varType: null,
varId: null,
varName: null,
datasource: 'logs',
enable: { thresholds: false, legend: true, valueMapping: false },
sync: 0,
remark: ''
}
this.expressions.forEach((exp, index) => {
chart.elements.push({ state: this.promqlKeys[index].state, expression: exp, legend: '', type: 'expert', id: '', name: this.transformNumToLetter(index) })
})
this.chartData = chart
this.rightBox.show = true
},
createSuccess (panel) { // 添加chart成功
2021-08-02 19:51:53 +08:00
this.$confirm(this.$t('dashboard.metric.goPanelTip'), this.$t('tip.saveSuccess'), {
confirmButtonText: this.$t('tip.yes'),
cancelButtonText: this.$t('tip.no'),
type: 'success'
}).then(() => {
bus.$emit('menu-change', 'panel')
this.$store.commit('panelShowPanelChange', panel)
this.$router.push({
path: '/panel',
query: {
t: +new Date()
}
})
})
},
getPanelData () { // 获取panel数据
this.$get('visual/panel?pageNo=1&pageSize=-1').then(response => {
if (response.code === 200) {
this.panelData = response.data.list
}
})
},
jumpTo (data, id) {
bus.$emit('menu-change', data)
this.$router.push({
path: '/' + data,
query: {
t: +new Date()
}
})
},
logsCollapseChange (activeNames, a, b) {
this.$nextTick(() => {
if (this.$refs.exploreChart) {
this.$refs.exploreChart.resize()
}
if (this.$refs.logChart) {
this.$refs.logChart.resize()
}
if (this.$refs.logDetail) {
this.$refs.logDetail.myChart.resize()
}
})
},
updateCustomTableTitle (custom) {
this.tools.customTableTitle = custom
this.$refs.exploreTable.doLayout()
},
transformNumToLetter (num) { // 相当于26进制 获取idaddExpression
const self = this
let letter = ''
const loopNum = parseInt(num / 26)
if (loopNum > 0) {
letter += this.transformNumToLetter(loopNum - 1)
}
letter += self.letter[num % 26]
return letter
},
exportToHtml (name) {
const params = {
type: this.showMetrics ? 1 : 2,
start: this.momentStrToTimestamp(this.filterTime[0]) / 1000,
end: this.momentStrToTimestamp(this.filterTime[1]) / 1000,
unit: this.chartUnit,
expressions: this.expressions.map(item => encodeURIComponent(item))
}
if (!this.showMetrics) {
params.limit = this.$refs.logDetail ? this.$refs.logDetail.getLimit() : 100
}
this.$get('/visual/explore/snapshot', params, 'blob').then(res => {
const self = this
let fileName = this.showMetrics ? 'Metrics explore' : 'Logs explore'
const resFileName = ''
if (resFileName) {
fileName = resFileName
}
if (res.type == 'application/json') {
const reader = new FileReader() // 创建一个FileReader实例
reader.readAsText(res, 'utf-8') // 读取文件,结果用字符串形式表示
reader.onload = function () { // 读取完成后,**获取reader.result**
const { msg } = JSON.parse(reader.result)
self.$message.error(msg) // 弹出错误提示
}
return
}
if (window.navigator.msSaveOrOpenBlob) {
// 兼容ie11
const blobObject = new Blob([res])
window.navigator.msSaveOrOpenBlob(blobObject, fileName + '.html')
} else {
const blob = new Blob([res])
const link = document.createElement('a')
const href = window.URL.createObjectURL(blob) // 下载链接
link.href = href
link.download = fileName + '.html' // 下载后文件名
document.body.appendChild(link)
link.click() // 点击下载
document.body.removeChild(link) // 下载完成移除元素
window.URL.revokeObjectURL(href) // 释放blob对象
}
this.$refs.topTool.closeDialog()
}, () => {
this.$message.error('123')
})
2021-08-02 19:51:53 +08:00
}
},
watch: {
promqlCount (n, o) {
this.expressionChange()
},
showMetrics (n, o) { // 更换type后将折叠面板都置为展开状态
if (n !== o) {
this.collapseValue = ['1', '2']
}
},
expressions: {
immediate: true,
handler (n, o) {
// if (n.length == 1 && (!n[0] || n[0] == '')) {
// this.showIntroduce = true
// } else if (n.length > 1) {
// const temp = n.find((item, index) => {
// return item != ''
// })
// if (!temp) {
// this.showIntroduce = true
// } else {
// // this.showIntroduce = false
// }
// } else {
// // this.showIntroduce = false
// }
const temp = n.some((item, index) => {
return item != '' && this.promqlKeys[index].state
})
if (!temp) {
2021-08-02 19:51:53 +08:00
this.showIntroduce = true
}
}
}
}
}
</script>