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> {
|
||||
const apiUrl = this.kubeProxyUrl + path;
|
||||
return request(apiUrl, {
|
||||
json: true,
|
||||
timeout: 30000,
|
||||
...options,
|
||||
headers: {
|
||||
Host: `${this.id}.${new URL(this.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||
...(options.headers || {}),
|
||||
},
|
||||
})
|
||||
options.headers ??= {}
|
||||
options.json ??= true
|
||||
options.timeout ??= 30000
|
||||
options.headers.Host = `${this.id}.${new URL(this.kubeProxyUrl).host}` // required in ClusterManager.getClusterForRequest()
|
||||
|
||||
return request(this.kubeProxyUrl + path, options)
|
||||
}
|
||||
|
||||
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
|
||||
|
||||
@ -1,72 +1,73 @@
|
||||
import { LensApiRequest } from "../router"
|
||||
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[] | {
|
||||
[metricName: string]: string;
|
||||
}
|
||||
|
||||
// This is used for backoff retry tracking.
|
||||
const MAX_ATTEMPTS = 5
|
||||
const ATTEMPTS = [...(_.fill(Array(MAX_ATTEMPTS - 1), false)), true]
|
||||
|
||||
// prometheus metrics loader
|
||||
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
|
||||
const queries = promQueries.map(p => p.trim())
|
||||
const loaders = new Map<string, Promise<any>>()
|
||||
|
||||
async function loadMetric(query: string): Promise<any> {
|
||||
async function loadMetricHelper(): Promise<any> {
|
||||
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
|
||||
try {
|
||||
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(request: LensApiRequest) {
|
||||
const { response, cluster, payload } = request
|
||||
const queryParams: IMetricsQuery = {}
|
||||
request.query.forEach((value: string, key: string) => {
|
||||
queryParams[key] = value
|
||||
})
|
||||
let prometheusPath: string
|
||||
let prometheusProvider: PrometheusProvider
|
||||
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries())
|
||||
|
||||
try {
|
||||
[prometheusPath, prometheusProvider] = await Promise.all([
|
||||
const [prometheusPath, prometheusProvider] = await Promise.all([
|
||||
cluster.contextHandler.getPrometheusPath(),
|
||||
cluster.contextHandler.getPrometheusProvider()
|
||||
])
|
||||
|
||||
// return data in same structure as query
|
||||
if (typeof payload === "string") {
|
||||
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams)
|
||||
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, {})
|
||||
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
|
||||
let data: any;
|
||||
if (typeof payload === "string") {
|
||||
data = await loadMetrics(payload)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user