mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
cleanup main side of metrics route for readability (#988)
* cleanup main side of metrics route for readability * split out ??= for headers Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
9d6158a620
commit
25be6401e2
@ -253,16 +253,12 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||||
const apiUrl = this.kubeProxyUrl + path;
|
options.headers ??= {}
|
||||||
return request(apiUrl, {
|
options.json ??= true
|
||||||
json: true,
|
options.timeout ??= 30000
|
||||||
timeout: 30000,
|
options.headers.Host = `${this.id}.${new URL(this.kubeProxyUrl).host}` // required in ClusterManager.getClusterForRequest()
|
||||||
...options,
|
|
||||||
headers: {
|
return request(this.kubeProxyUrl + path, options)
|
||||||
Host: `${this.id}.${new URL(this.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
|
||||||
...(options.headers || {}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
|
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
|
||||||
|
|||||||
@ -1,72 +1,73 @@
|
|||||||
import { LensApiRequest } from "../router"
|
import { LensApiRequest } from "../router"
|
||||||
import { LensApi } from "../lens-api"
|
import { LensApi } from "../lens-api"
|
||||||
import { PrometheusClusterQuery, PrometheusIngressQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusProvider, PrometheusPvcQuery, PrometheusQueryOpts } from "../prometheus/provider-registry"
|
import { Cluster } from "../cluster"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
export type IMetricsQuery = string | string[] | {
|
export type IMetricsQuery = string | string[] | {
|
||||||
[metricName: string]: string;
|
[metricName: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetricsRoute extends LensApi {
|
// This is used for backoff retry tracking.
|
||||||
async routeMetrics(request: LensApiRequest) {
|
const MAX_ATTEMPTS = 5
|
||||||
const { response, cluster, payload } = request
|
const ATTEMPTS = [...(_.fill(Array(MAX_ATTEMPTS - 1), false)), true]
|
||||||
const queryParams: IMetricsQuery = {}
|
|
||||||
request.query.forEach((value: string, key: string) => {
|
// prometheus metrics loader
|
||||||
queryParams[key] = value
|
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
|
||||||
})
|
const queries = promQueries.map(p => p.trim())
|
||||||
let prometheusPath: string
|
const loaders = new Map<string, Promise<any>>()
|
||||||
let prometheusProvider: PrometheusProvider
|
|
||||||
|
async function loadMetric(query: string): Promise<any> {
|
||||||
|
async function loadMetricHelper(): Promise<any> {
|
||||||
|
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
|
||||||
try {
|
try {
|
||||||
[prometheusPath, prometheusProvider] = await Promise.all([
|
return await cluster.getMetrics(prometheusPath, { query, ...queryParams })
|
||||||
|
} catch (error) {
|
||||||
|
if (lastAttempt || error?.statusCode === 404) {
|
||||||
|
return {
|
||||||
|
status: error.toString(),
|
||||||
|
data: { result: [] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(queries.map(loadMetric))
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsRoute extends LensApi {
|
||||||
|
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||||
|
const queryParams: IMetricsQuery = Object.fromEntries(query.entries())
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [prometheusPath, prometheusProvider] = await Promise.all([
|
||||||
cluster.contextHandler.getPrometheusPath(),
|
cluster.contextHandler.getPrometheusPath(),
|
||||||
cluster.contextHandler.getPrometheusProvider()
|
cluster.contextHandler.getPrometheusProvider()
|
||||||
])
|
])
|
||||||
} catch {
|
|
||||||
this.respondJson(response, {})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// prometheus metrics loader
|
|
||||||
const attempts: { [query: string]: number } = {};
|
|
||||||
const maxAttempts = 5;
|
|
||||||
const loadMetrics = (promQuery: string): Promise<any> => {
|
|
||||||
const query = promQuery.trim()
|
|
||||||
const attempt = attempts[query] = (attempts[query] || 0) + 1;
|
|
||||||
return cluster.getMetrics(prometheusPath, { query, ...queryParams }).catch(async error => {
|
|
||||||
if (attempt < maxAttempts && (error.statusCode && error.statusCode != 404)) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, attempt * 1000)); // add delay before repeating request
|
|
||||||
return loadMetrics(query);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: error.toString(),
|
|
||||||
data: {
|
|
||||||
result: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// return data in same structure as query
|
// return data in same structure as query
|
||||||
let data: any;
|
|
||||||
if (typeof payload === "string") {
|
if (typeof payload === "string") {
|
||||||
data = await loadMetrics(payload)
|
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams)
|
||||||
} else if (Array.isArray(payload)) {
|
|
||||||
data = await Promise.all(payload.map(loadMetrics));
|
|
||||||
} else {
|
|
||||||
data = {};
|
|
||||||
const result = await Promise.all(
|
|
||||||
Object.entries(payload).map((queryEntry: any) => {
|
|
||||||
const queryName: string = queryEntry[0]
|
|
||||||
const queryOpts: PrometheusQueryOpts = queryEntry[1]
|
|
||||||
const queries = prometheusProvider.getQueries(queryOpts)
|
|
||||||
const q = queries[queryName as keyof (PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery)]
|
|
||||||
return loadMetrics(q)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
Object.keys(payload).forEach((metricName, index) => {
|
|
||||||
data[metricName] = result[index];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.respondJson(response, data)
|
this.respondJson(response, data)
|
||||||
|
} else if (Array.isArray(payload)) {
|
||||||
|
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams)
|
||||||
|
this.respondJson(response, data)
|
||||||
|
} else {
|
||||||
|
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
|
||||||
|
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
|
||||||
|
))
|
||||||
|
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams)
|
||||||
|
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]))
|
||||||
|
this.respondJson(response, data)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.respondJson(response, {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user