1
0
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:
Sebastian Malton 2020-11-16 10:43:53 -05:00 committed by GitHub
parent 9d6158a620
commit 25be6401e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 64 deletions

View File

@ -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 }) {

View File

@ -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, {})
}
} }
} }