mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Refactor PrometheusProviders (#2707)
This commit is contained in:
parent
88edeeef43
commit
652319ac89
210
src/main/__test__/context-handler.test.ts
Normal file
210
src/main/__test__/context-handler.test.ts
Normal file
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import { ContextHandler } from "../context-handler";
|
||||
import { PrometheusProvider, PrometheusProviderRegistry, PrometheusService } from "../prometheus";
|
||||
|
||||
enum ServiceResult {
|
||||
Success,
|
||||
Failure,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
class TestProvider extends PrometheusProvider {
|
||||
name = "TestProvider1";
|
||||
rateAccuracy = "1h";
|
||||
isConfigurable = false;
|
||||
|
||||
constructor(public id: string, public alwaysFail: ServiceResult) {
|
||||
super();
|
||||
}
|
||||
|
||||
getQuery(): string {
|
||||
throw new Error("getQuery is not implemented.");
|
||||
}
|
||||
|
||||
async getPrometheusService(): Promise<PrometheusService> {
|
||||
switch (this.alwaysFail) {
|
||||
case ServiceResult.Success:
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: "default",
|
||||
port: 7000,
|
||||
service: "",
|
||||
};
|
||||
case ServiceResult.Failure:
|
||||
throw new Error("does fail");
|
||||
case ServiceResult.Undefined:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHandler() {
|
||||
return new ContextHandler(({
|
||||
getProxyKubeconfig: (): any => ({
|
||||
makeApiClient: (): any => undefined,
|
||||
}),
|
||||
apiUrl: "http://localhost:81",
|
||||
}) as any);
|
||||
}
|
||||
|
||||
describe("ContextHandler", () => {
|
||||
beforeEach(() => {
|
||||
PrometheusProviderRegistry.createInstance();
|
||||
UserStore.createInstance();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
PrometheusProviderRegistry.resetInstance();
|
||||
UserStore.resetInstance();
|
||||
});
|
||||
|
||||
describe("getPrometheusService", () => {
|
||||
it.each([
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
])("should return undefined from %d success(es) after %d failure(s)", async (successes, failures) => {
|
||||
const reg = PrometheusProviderRegistry.getInstance();
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
||||
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
||||
}
|
||||
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
}
|
||||
|
||||
const service = await getHandler().getPrometheusService();
|
||||
|
||||
expect(service).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
|
||||
const reg = PrometheusProviderRegistry.getInstance();
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
||||
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
||||
}
|
||||
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
}
|
||||
|
||||
const service = await getHandler().getPrometheusService();
|
||||
|
||||
expect(service.id === `id_${failures}`);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
|
||||
const reg = PrometheusProviderRegistry.getInstance();
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
}
|
||||
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
||||
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
||||
}
|
||||
|
||||
const service = await getHandler().getPrometheusService();
|
||||
|
||||
expect(service.id === "id_0");
|
||||
});
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
|
||||
const reg = PrometheusProviderRegistry.getInstance();
|
||||
let count = 0;
|
||||
const beforeSuccesses = Math.floor(successes / 2);
|
||||
const afterSuccesses = successes - beforeSuccesses;
|
||||
|
||||
for (let i = 0; i < beforeSuccesses; i += 1) {
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
}
|
||||
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
||||
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
||||
}
|
||||
|
||||
for (let i = 0; i < afterSuccesses; i += 1) {
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
}
|
||||
|
||||
const service = await getHandler().getPrometheusService();
|
||||
|
||||
expect(service.id === "id_0");
|
||||
});
|
||||
|
||||
it("shouldn't pick the second provider of 2 succcess(es) after 1 failure(s)", async () => {
|
||||
const reg = PrometheusProviderRegistry.getInstance();
|
||||
let count = 0;
|
||||
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||
|
||||
const service = await getHandler().getPrometheusService();
|
||||
|
||||
expect(service.id).not.toBe("id_2");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -19,13 +19,13 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { PrometheusService } from "./prometheus/provider-registry";
|
||||
import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry";
|
||||
import { PrometheusProviderRegistry } from "./prometheus/provider-registry";
|
||||
import type { ClusterPrometheusPreferences } from "../common/cluster-store";
|
||||
import type { Cluster } from "./cluster";
|
||||
import type httpProxy from "http-proxy";
|
||||
import url, { UrlWithStringQuery } from "url";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import { prometheusProviders } from "../common/prometheus-providers";
|
||||
import logger from "./logger";
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||
|
||||
@ -33,7 +33,7 @@ export class ContextHandler {
|
||||
public clusterUrl: UrlWithStringQuery;
|
||||
protected kubeAuthProxy?: KubeAuthProxy;
|
||||
protected apiTarget?: httpProxy.ServerOptions;
|
||||
protected prometheusProvider: string;
|
||||
protected prometheusProvider?: string;
|
||||
protected prometheusPath: string | null;
|
||||
|
||||
constructor(protected cluster: Cluster) {
|
||||
@ -72,18 +72,34 @@ export class ContextHandler {
|
||||
this.prometheusProvider = service.id;
|
||||
}
|
||||
|
||||
return prometheusProviders.find(p => p.id === this.prometheusProvider);
|
||||
return PrometheusProviderRegistry.getInstance().getByKind(this.prometheusProvider);
|
||||
}
|
||||
|
||||
async getPrometheusService(): Promise<PrometheusService | void> {
|
||||
const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders;
|
||||
const prometheusPromises: Promise<PrometheusService | void>[] = providers.map(async provider => {
|
||||
const apiClient = (await this.cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
|
||||
protected listPotentialProviders(): PrometheusProvider[] {
|
||||
const registry = PrometheusProviderRegistry.getInstance();
|
||||
|
||||
return provider.getPrometheusService(apiClient);
|
||||
});
|
||||
if (typeof this.prometheusProvider === "string") {
|
||||
return [registry.getByKind(this.prometheusProvider)];
|
||||
}
|
||||
|
||||
return (await Promise.all(prometheusPromises)).find(Boolean);
|
||||
return Array.from(registry.providers.values());
|
||||
}
|
||||
|
||||
async getPrometheusService(): Promise<PrometheusService | undefined> {
|
||||
const providers = this.listPotentialProviders();
|
||||
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
||||
const apiClient = proxyConfig.makeApiClient(CoreV1Api);
|
||||
const potentialServices = await Promise.allSettled(
|
||||
providers.map(provider => provider.getPrometheusService(apiClient))
|
||||
);
|
||||
|
||||
for (const result of potentialServices) {
|
||||
if (result.status === "fulfilled" && result.value) {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getPrometheusPath(): Promise<string> {
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
// Main process
|
||||
|
||||
import "../common/system-ca";
|
||||
import "../common/prometheus-providers";
|
||||
import * as Mobx from "mobx";
|
||||
import * as LensExtensionsCoreApi from "../extensions/core-api";
|
||||
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
|
||||
@ -56,6 +55,7 @@ import { HelmRepoManager } from "./helm/helm-repo-manager";
|
||||
import { KubeconfigSyncManager } from "./catalog-sources";
|
||||
import { handleWsUpgrade } from "./proxy/ws-upgrade";
|
||||
import configurePackages from "../common/configure-packages";
|
||||
import { PrometheusProviderRegistry, registerDefaultPrometheusProviders } from "./prometheus";
|
||||
|
||||
const workingDir = path.join(app.getPath("appData"), appName);
|
||||
const cleanup = disposer();
|
||||
@ -124,6 +124,9 @@ app.on("ready", async () => {
|
||||
|
||||
registerFileProtocol("static", __static);
|
||||
|
||||
PrometheusProviderRegistry.createInstance();
|
||||
registerDefaultPrometheusProviders();
|
||||
|
||||
const userStore = UserStore.createInstance();
|
||||
const clusterStore = ClusterStore.createInstance();
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
|
||||
@ -22,32 +22,14 @@
|
||||
import { PrometheusLens } from "./lens";
|
||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||
import type { PrometheusService } from "./provider-registry";
|
||||
import logger from "../logger";
|
||||
|
||||
export class PrometheusHelm extends PrometheusLens {
|
||||
id = "helm";
|
||||
name = "Helm";
|
||||
rateAccuracy = "5m";
|
||||
readonly id: string = "helm";
|
||||
readonly name: string = "Helm";
|
||||
readonly rateAccuracy: string = "5m";
|
||||
readonly isConfigurable: boolean = false;
|
||||
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
||||
const labelSelector = "app=prometheus,component=server,heritage=Helm";
|
||||
|
||||
try {
|
||||
const serviceList = await client.listServiceForAllNamespaces(false, "", null, labelSelector);
|
||||
const service = serviceList.body.items[0];
|
||||
|
||||
if (!service) return;
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
};
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusHelm: failed to list services: ${error.toString()}`);
|
||||
|
||||
return;
|
||||
}
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||
return this.getFirstNamespacedServer(client, "app=prometheus,component=server,heritage=Helm");
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,16 +19,19 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { PrometheusLens } from "../main/prometheus/lens";
|
||||
import { PrometheusHelm } from "../main/prometheus/helm";
|
||||
import { PrometheusOperator } from "../main/prometheus/operator";
|
||||
import { PrometheusStacklight } from "../main/prometheus/stacklight";
|
||||
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
|
||||
import { PrometheusHelm } from "./helm";
|
||||
import { PrometheusLens } from "./lens";
|
||||
import { PrometheusOperator } from "./operator";
|
||||
import { PrometheusProviderRegistry } from "./provider-registry";
|
||||
import { PrometheusStacklight } from "./stacklight";
|
||||
|
||||
[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
|
||||
const provider = new providerClass();
|
||||
export * from "./provider-registry";
|
||||
|
||||
PrometheusProviderRegistry.registerProvider(provider.id, provider);
|
||||
});
|
||||
|
||||
export const prometheusProviders = PrometheusProviderRegistry.getProviders();
|
||||
export function registerDefaultPrometheusProviders() {
|
||||
PrometheusProviderRegistry
|
||||
.getInstance()
|
||||
.registerProvider(new PrometheusLens())
|
||||
.registerProvider(new PrometheusHelm())
|
||||
.registerProvider(new PrometheusOperator())
|
||||
.registerProvider(new PrometheusStacklight());
|
||||
}
|
||||
@ -19,16 +19,18 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
|
||||
import { PrometheusProvider, PrometheusService } from "./provider-registry";
|
||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||
import logger from "../logger";
|
||||
import { inspect } from "util";
|
||||
|
||||
export class PrometheusLens implements PrometheusProvider {
|
||||
id = "lens";
|
||||
name = "Lens";
|
||||
rateAccuracy = "1m";
|
||||
export class PrometheusLens extends PrometheusProvider {
|
||||
readonly id: string = "lens";
|
||||
readonly name: string = "Lens";
|
||||
readonly rateAccuracy: string = "1m";
|
||||
readonly isConfigurable: boolean = true;
|
||||
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||
try {
|
||||
const resp = await client.readNamespacedService("prometheus", "lens-metrics");
|
||||
const service = resp.body;
|
||||
@ -41,66 +43,101 @@ export class PrometheusLens implements PrometheusProvider {
|
||||
};
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void {
|
||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
return {
|
||||
memoryUsage: `
|
||||
sum(
|
||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
||||
) by (kubernetes_name)
|
||||
`.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`),
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`,
|
||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`,
|
||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`,
|
||||
fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_name)`.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`);
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "podUsage":
|
||||
return `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`;
|
||||
case "podCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`;
|
||||
case "fsUsage":
|
||||
return `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`;
|
||||
}
|
||||
break;
|
||||
case "nodes":
|
||||
return {
|
||||
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`,
|
||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`,
|
||||
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`;
|
||||
case "fsUsage":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)`;
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
return {
|
||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryUsage":
|
||||
return `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "fsUsage":
|
||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "networkReceive":
|
||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "networkTransmit":
|
||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
return {
|
||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
case "diskCapacity":
|
||||
return `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||
case "bytesSentFailure":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||
case "requestDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
case "responseDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
||||
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,98 +19,110 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
|
||||
import type { CoreV1Api, V1Service } from "@kubernetes/client-node";
|
||||
import logger from "../logger";
|
||||
import { PrometheusProvider, PrometheusService } from "./provider-registry";
|
||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||
import { inspect } from "util";
|
||||
|
||||
export class PrometheusOperator implements PrometheusProvider {
|
||||
rateAccuracy = "1m";
|
||||
id = "operator";
|
||||
name = "Prometheus Operator";
|
||||
export class PrometheusOperator extends PrometheusProvider {
|
||||
readonly rateAccuracy: string = "1m";
|
||||
readonly id: string = "operator";
|
||||
readonly name: string = "Prometheus Operator";
|
||||
readonly isConfigurable: boolean = false;
|
||||
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
||||
try {
|
||||
let service: V1Service;
|
||||
|
||||
for (const labelSelector of ["operated-prometheus=true", "self-monitor=true"]) {
|
||||
if (!service) {
|
||||
const serviceList = await client.listServiceForAllNamespaces(null, null, null, labelSelector);
|
||||
|
||||
service = serviceList.body.items[0];
|
||||
}
|
||||
}
|
||||
if (!service) return;
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
};
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusOperator: failed to list services: ${error.toString()}`);
|
||||
|
||||
return;
|
||||
}
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||
return this.getFirstNamespacedServer(client, "operated-prometheus=true", "self-monitor=true");
|
||||
}
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void {
|
||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
return {
|
||||
memoryUsage: `
|
||||
sum(
|
||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
||||
)
|
||||
`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`),
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
|
||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`,
|
||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`,
|
||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", node=~"${opts.nodes}"})`,
|
||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
|
||||
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`);
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`;
|
||||
case "podUsage":
|
||||
return `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", node=~"${opts.nodes}"})`;
|
||||
case "podCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||
case "fsUsage":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||
}
|
||||
break;
|
||||
case "nodes":
|
||||
return {
|
||||
memoryUsage: `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
|
||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
|
||||
fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`;
|
||||
case "fsUsage":
|
||||
return `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`;
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
return {
|
||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryUsage":
|
||||
return `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "fsUsage":
|
||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "networkReceive":
|
||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "networkTransmit":
|
||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
return {
|
||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
case "diskCapacity":
|
||||
return `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||
case "bytesSentFailure":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||
case "requestDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
case "responseDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
||||
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,58 +20,8 @@
|
||||
*/
|
||||
|
||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||
|
||||
export type PrometheusClusterQuery = {
|
||||
memoryUsage: string;
|
||||
memoryRequests: string;
|
||||
memoryLimits: string;
|
||||
memoryCapacity: string;
|
||||
cpuUsage: string;
|
||||
cpuRequests: string;
|
||||
cpuLimits: string;
|
||||
cpuCapacity: string;
|
||||
podUsage: string;
|
||||
podCapacity: string;
|
||||
};
|
||||
|
||||
export type PrometheusNodeQuery = {
|
||||
memoryUsage: string;
|
||||
memoryCapacity: string;
|
||||
cpuUsage: string;
|
||||
cpuCapacity: string;
|
||||
fsSize: string;
|
||||
fsUsage: string;
|
||||
};
|
||||
|
||||
export type PrometheusPodQuery = {
|
||||
memoryUsage: string;
|
||||
memoryRequests: string;
|
||||
memoryLimits: string;
|
||||
cpuUsage: string;
|
||||
cpuRequests: string;
|
||||
cpuLimits: string;
|
||||
fsUsage: string;
|
||||
networkReceive: string;
|
||||
networkTransmit: string;
|
||||
};
|
||||
|
||||
export type PrometheusPvcQuery = {
|
||||
diskUsage: string;
|
||||
diskCapacity: string;
|
||||
};
|
||||
|
||||
export type PrometheusIngressQuery = {
|
||||
bytesSentSuccess: string;
|
||||
bytesSentFailure: string;
|
||||
requestDurationSeconds: string;
|
||||
responseDurationSeconds: string;
|
||||
};
|
||||
|
||||
export type PrometheusQueryOpts = {
|
||||
[key: string]: string | any;
|
||||
};
|
||||
|
||||
export type PrometheusQuery = PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery;
|
||||
import { Singleton } from "../../common/utils";
|
||||
import logger from "../logger";
|
||||
|
||||
export type PrometheusService = {
|
||||
id: string;
|
||||
@ -80,33 +30,61 @@ export type PrometheusService = {
|
||||
port: number;
|
||||
};
|
||||
|
||||
export interface PrometheusProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void;
|
||||
getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void>;
|
||||
export abstract class PrometheusProvider {
|
||||
abstract readonly id: string;
|
||||
abstract readonly name: string;
|
||||
abstract readonly rateAccuracy: string;
|
||||
abstract readonly isConfigurable: boolean;
|
||||
|
||||
abstract getQuery(opts: Record<string, string>, queryName: string): string;
|
||||
abstract getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined>;
|
||||
|
||||
protected bytesSent(ingress: string, namespace: string, statuses: string): string {
|
||||
return `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
}
|
||||
|
||||
export type PrometheusProviderList = {
|
||||
[key: string]: PrometheusProvider;
|
||||
protected async getFirstNamespacedServer(client: CoreV1Api, ...selectors: string[]): Promise<PrometheusService | undefined> {
|
||||
try {
|
||||
for (const selector of selectors) {
|
||||
const { body: { items: [service] } } = await client.listServiceForAllNamespaces(null, null, null, selector);
|
||||
|
||||
if (service) {
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
};
|
||||
|
||||
export class PrometheusProviderRegistry {
|
||||
private static prometheusProviders: PrometheusProviderList = {};
|
||||
|
||||
static getProvider(type: string): PrometheusProvider {
|
||||
if (!this.prometheusProviders[type]) {
|
||||
throw "Unknown Prometheus provider";
|
||||
}
|
||||
|
||||
return this.prometheusProviders[type];
|
||||
}
|
||||
|
||||
static registerProvider(key: string, provider: PrometheusProvider) {
|
||||
this.prometheusProviders[key] = provider;
|
||||
}
|
||||
|
||||
static getProviders(): PrometheusProvider[] {
|
||||
return Object.values(this.prometheusProviders);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`${this.name}: failed to list services: ${error.toString()}`);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class PrometheusProviderRegistry extends Singleton {
|
||||
public providers = new Map<string, PrometheusProvider>();
|
||||
|
||||
getByKind(kind: string): PrometheusProvider {
|
||||
const provider = this.providers.get(kind);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error("Unknown Prometheus provider");
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
registerProvider(provider: PrometheusProvider): this {
|
||||
if (this.providers.has(provider.id)) {
|
||||
throw new Error("Provider already registered under that kind");
|
||||
}
|
||||
|
||||
this.providers.set(provider.id, provider);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,16 +19,18 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
|
||||
import { PrometheusProvider, PrometheusService } from "./provider-registry";
|
||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||
import logger from "../logger";
|
||||
import { inspect } from "util";
|
||||
|
||||
export class PrometheusStacklight implements PrometheusProvider {
|
||||
id = "stacklight";
|
||||
name = "Stacklight";
|
||||
rateAccuracy = "1m";
|
||||
export class PrometheusStacklight extends PrometheusProvider {
|
||||
readonly id: string = "stacklight";
|
||||
readonly name: string = "Stacklight";
|
||||
readonly rateAccuracy: string = "1m";
|
||||
readonly isConfigurable: boolean = false;
|
||||
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||
try {
|
||||
const resp = await client.readNamespacedService("prometheus-server", "stacklight");
|
||||
const service = resp.body;
|
||||
@ -41,66 +43,101 @@ export class PrometheusStacklight implements PrometheusProvider {
|
||||
};
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void {
|
||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
return {
|
||||
memoryUsage: `
|
||||
sum(
|
||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
||||
) by (kubernetes_name)
|
||||
`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`),
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`,
|
||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`,
|
||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`,
|
||||
fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_name)`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`);
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||
case "podUsage":
|
||||
return `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`;
|
||||
case "podCapacity":
|
||||
return `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`;
|
||||
case "fsUsage":
|
||||
return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`;
|
||||
}
|
||||
break;
|
||||
case "nodes":
|
||||
return {
|
||||
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`,
|
||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`,
|
||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`,
|
||||
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`;
|
||||
case "memoryCapacity":
|
||||
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||
case "cpuUsage":
|
||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`;
|
||||
case "cpuCapacity":
|
||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||
case "fsSize":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`;
|
||||
case "fsUsage":
|
||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`;
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
return {
|
||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "cpuRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "cpuLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryUsage":
|
||||
return `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryRequests":
|
||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "memoryLimits":
|
||||
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "fsUsage":
|
||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||
case "networkReceive":
|
||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
case "networkTransmit":
|
||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
return {
|
||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
||||
};
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
case "diskCapacity":
|
||||
return `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||
case "bytesSentFailure":
|
||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||
case "requestDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
case "responseDurationSeconds":
|
||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
||||
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,6 +177,7 @@ export class Router {
|
||||
|
||||
// Metrics API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||
|
||||
// Port-forward API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, PortForwardRoute.routePortForward);
|
||||
|
||||
@ -26,6 +26,7 @@ import { Cluster, ClusterMetadataKey } from "../cluster";
|
||||
import type { ClusterPrometheusMetadata } from "../../common/cluster-store";
|
||||
import logger from "../logger";
|
||||
import { getMetrics } from "../k8s-request";
|
||||
import { PrometheusProviderRegistry } from "../prometheus";
|
||||
|
||||
export type IMetricsQuery = string | string[] | {
|
||||
[metricName: string]: string;
|
||||
@ -62,6 +63,12 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
|
||||
return Promise.all(queries.map(loadMetric));
|
||||
}
|
||||
|
||||
interface MetricProviderInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
isConfigurable: boolean;
|
||||
}
|
||||
|
||||
export class MetricsRoute {
|
||||
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
||||
@ -92,8 +99,9 @@ export class MetricsRoute {
|
||||
|
||||
respondJson(response, data);
|
||||
} else {
|
||||
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
|
||||
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
|
||||
const queries = Object.entries<Record<string, string>>(payload)
|
||||
.map(([queryName, queryOpts]) => (
|
||||
prometheusProvider.getQuery(queryOpts, queryName)
|
||||
));
|
||||
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
||||
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
||||
@ -108,4 +116,14 @@ export class MetricsRoute {
|
||||
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
static async routeMetricsProviders({ response }: LensApiRequest) {
|
||||
const providers: MetricProviderInfo[] = [];
|
||||
|
||||
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) {
|
||||
providers.push({ name, id, isConfigurable });
|
||||
}
|
||||
|
||||
respondJson(response, providers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +46,12 @@ export interface IMetricsResult {
|
||||
values: [number, string][];
|
||||
}
|
||||
|
||||
export interface MetricProviderInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
isConfigurable: boolean;
|
||||
}
|
||||
|
||||
export interface IMetricsReqParams {
|
||||
start?: number | string; // timestamp in seconds or valid date-string
|
||||
end?: number | string;
|
||||
@ -75,6 +81,10 @@ export const metricsApi = {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async getMetricProviders(): Promise<MetricProviderInfo[]> {
|
||||
return apiBase.get("/metrics/providers");
|
||||
}
|
||||
};
|
||||
|
||||
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
|
||||
@ -21,18 +21,14 @@
|
||||
|
||||
import React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { prometheusProviders } from "../../../../common/prometheus-providers";
|
||||
import type { Cluster } from "../../../../main/cluster";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { Input } from "../../input";
|
||||
import { observable, computed, autorun, makeObservable } from "mobx";
|
||||
import { productName } from "../../../../common/vars";
|
||||
|
||||
const options: SelectOption<string>[] = [
|
||||
{ value: "", label: "Auto detect" },
|
||||
...prometheusProviders.map(pp => ({value: pp.id, label: pp.name}))
|
||||
];
|
||||
import { MetricProviderInfo, metricsApi } from "../../../api/endpoints/metrics.api";
|
||||
import { Spinner } from "../../spinner";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
@ -42,16 +38,27 @@ interface Props {
|
||||
export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
@observable path = "";
|
||||
@observable provider = "";
|
||||
@observable loading = true;
|
||||
@observable loadedOptions: MetricProviderInfo[] = [];
|
||||
|
||||
@computed get options(): SelectOption<string>[] {
|
||||
return [
|
||||
{ value: "", label: "Auto detect" },
|
||||
...this.loadedOptions.map(({ name, id }) => ({ value: id, label: name }))
|
||||
];
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get canEditPrometheusPath() {
|
||||
if (this.provider === "" || this.provider === "lens") return false;
|
||||
|
||||
return true;
|
||||
@computed get canEditPrometheusPath(): boolean {
|
||||
return Boolean(
|
||||
this.loadedOptions
|
||||
.find(opt => opt.id === this.provider)
|
||||
?.isConfigurable
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -74,6 +81,16 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
metricsApi
|
||||
.getMetricProviders()
|
||||
.then(values => {
|
||||
this.loading = false;
|
||||
|
||||
if (values) {
|
||||
this.loadedOptions = values;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
@ -110,15 +127,21 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
<>
|
||||
<section>
|
||||
<SubTitle title="Prometheus"/>
|
||||
{
|
||||
this.loading
|
||||
? <Spinner />
|
||||
: <>
|
||||
<Select
|
||||
value={this.provider}
|
||||
onChange={({ value }) => {
|
||||
this.provider = value;
|
||||
this.onSaveProvider();
|
||||
}}
|
||||
options={options}
|
||||
options={this.options}
|
||||
/>
|
||||
<small className="hint">What query format is used to fetch metrics from Prometheus</small>
|
||||
</>
|
||||
}
|
||||
</section>
|
||||
{this.canEditPrometheusPath && (
|
||||
<section>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user