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.
|
* 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 { ClusterPrometheusPreferences } from "../common/cluster-store";
|
||||||
import type { Cluster } from "./cluster";
|
import type { Cluster } from "./cluster";
|
||||||
import type httpProxy from "http-proxy";
|
import type httpProxy from "http-proxy";
|
||||||
import url, { UrlWithStringQuery } from "url";
|
import url, { UrlWithStringQuery } from "url";
|
||||||
import { CoreV1Api } from "@kubernetes/client-node";
|
import { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import { prometheusProviders } from "../common/prometheus-providers";
|
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export class ContextHandler {
|
|||||||
public clusterUrl: UrlWithStringQuery;
|
public clusterUrl: UrlWithStringQuery;
|
||||||
protected kubeAuthProxy?: KubeAuthProxy;
|
protected kubeAuthProxy?: KubeAuthProxy;
|
||||||
protected apiTarget?: httpProxy.ServerOptions;
|
protected apiTarget?: httpProxy.ServerOptions;
|
||||||
protected prometheusProvider: string;
|
protected prometheusProvider?: string;
|
||||||
protected prometheusPath: string | null;
|
protected prometheusPath: string | null;
|
||||||
|
|
||||||
constructor(protected cluster: Cluster) {
|
constructor(protected cluster: Cluster) {
|
||||||
@ -72,18 +72,34 @@ export class ContextHandler {
|
|||||||
this.prometheusProvider = service.id;
|
this.prometheusProvider = service.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return prometheusProviders.find(p => p.id === this.prometheusProvider);
|
return PrometheusProviderRegistry.getInstance().getByKind(this.prometheusProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrometheusService(): Promise<PrometheusService | void> {
|
protected listPotentialProviders(): PrometheusProvider[] {
|
||||||
const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders;
|
const registry = PrometheusProviderRegistry.getInstance();
|
||||||
const prometheusPromises: Promise<PrometheusService | void>[] = providers.map(async provider => {
|
|
||||||
const apiClient = (await this.cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
|
|
||||||
|
|
||||||
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> {
|
async getPrometheusPath(): Promise<string> {
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
// Main process
|
// Main process
|
||||||
|
|
||||||
import "../common/system-ca";
|
import "../common/system-ca";
|
||||||
import "../common/prometheus-providers";
|
|
||||||
import * as Mobx from "mobx";
|
import * as Mobx from "mobx";
|
||||||
import * as LensExtensionsCoreApi from "../extensions/core-api";
|
import * as LensExtensionsCoreApi from "../extensions/core-api";
|
||||||
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
|
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 { KubeconfigSyncManager } from "./catalog-sources";
|
||||||
import { handleWsUpgrade } from "./proxy/ws-upgrade";
|
import { handleWsUpgrade } from "./proxy/ws-upgrade";
|
||||||
import configurePackages from "../common/configure-packages";
|
import configurePackages from "../common/configure-packages";
|
||||||
|
import { PrometheusProviderRegistry, registerDefaultPrometheusProviders } from "./prometheus";
|
||||||
|
|
||||||
const workingDir = path.join(app.getPath("appData"), appName);
|
const workingDir = path.join(app.getPath("appData"), appName);
|
||||||
const cleanup = disposer();
|
const cleanup = disposer();
|
||||||
@ -124,6 +124,9 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
registerFileProtocol("static", __static);
|
registerFileProtocol("static", __static);
|
||||||
|
|
||||||
|
PrometheusProviderRegistry.createInstance();
|
||||||
|
registerDefaultPrometheusProviders();
|
||||||
|
|
||||||
const userStore = UserStore.createInstance();
|
const userStore = UserStore.createInstance();
|
||||||
const clusterStore = ClusterStore.createInstance();
|
const clusterStore = ClusterStore.createInstance();
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|||||||
@ -22,32 +22,14 @@
|
|||||||
import { PrometheusLens } from "./lens";
|
import { PrometheusLens } from "./lens";
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import type { PrometheusService } from "./provider-registry";
|
import type { PrometheusService } from "./provider-registry";
|
||||||
import logger from "../logger";
|
|
||||||
|
|
||||||
export class PrometheusHelm extends PrometheusLens {
|
export class PrometheusHelm extends PrometheusLens {
|
||||||
id = "helm";
|
readonly id: string = "helm";
|
||||||
name = "Helm";
|
readonly name: string = "Helm";
|
||||||
rateAccuracy = "5m";
|
readonly rateAccuracy: string = "5m";
|
||||||
|
readonly isConfigurable: boolean = false;
|
||||||
|
|
||||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||||
const labelSelector = "app=prometheus,component=server,heritage=Helm";
|
return this.getFirstNamespacedServer(client, "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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,16 +19,19 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PrometheusLens } from "../main/prometheus/lens";
|
import { PrometheusHelm } from "./helm";
|
||||||
import { PrometheusHelm } from "../main/prometheus/helm";
|
import { PrometheusLens } from "./lens";
|
||||||
import { PrometheusOperator } from "../main/prometheus/operator";
|
import { PrometheusOperator } from "./operator";
|
||||||
import { PrometheusStacklight } from "../main/prometheus/stacklight";
|
import { PrometheusProviderRegistry } from "./provider-registry";
|
||||||
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
|
import { PrometheusStacklight } from "./stacklight";
|
||||||
|
|
||||||
[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
|
export * from "./provider-registry";
|
||||||
const provider = new providerClass();
|
|
||||||
|
|
||||||
PrometheusProviderRegistry.registerProvider(provider.id, provider);
|
export function registerDefaultPrometheusProviders() {
|
||||||
});
|
PrometheusProviderRegistry
|
||||||
|
.getInstance()
|
||||||
export const prometheusProviders = PrometheusProviderRegistry.getProviders();
|
.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.
|
* 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 type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
import { inspect } from "util";
|
||||||
|
|
||||||
export class PrometheusLens implements PrometheusProvider {
|
export class PrometheusLens extends PrometheusProvider {
|
||||||
id = "lens";
|
readonly id: string = "lens";
|
||||||
name = "Lens";
|
readonly name: string = "Lens";
|
||||||
rateAccuracy = "1m";
|
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 {
|
try {
|
||||||
const resp = await client.readNamespacedService("prometheus", "lens-metrics");
|
const resp = await client.readNamespacedService("prometheus", "lens-metrics");
|
||||||
const service = resp.body;
|
const service = resp.body;
|
||||||
@ -41,66 +43,101 @@ export class PrometheusLens implements PrometheusProvider {
|
|||||||
};
|
};
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`);
|
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) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
return {
|
switch (queryName) {
|
||||||
memoryUsage: `
|
case "memoryUsage":
|
||||||
sum(
|
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}"}`);
|
||||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
case "memoryRequests":
|
||||||
) by (kubernetes_name)
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
`.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`),
|
case "memoryLimits":
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
case "memoryCapacity":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
return `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}]))`,
|
case "cpuUsage":
|
||||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
return `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
case "cpuRequests":
|
||||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`,
|
case "cpuLimits":
|
||||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`,
|
case "cpuCapacity":
|
||||||
fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`
|
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":
|
case "nodes":
|
||||||
return {
|
switch (queryName) {
|
||||||
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`,
|
case "memoryUsage":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`;
|
||||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`,
|
case "memoryCapacity":
|
||||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`,
|
case "cpuUsage":
|
||||||
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)`
|
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":
|
case "pods":
|
||||||
return {
|
switch (queryName) {
|
||||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
case "cpuUsage":
|
||||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuRequests":
|
||||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuLimits":
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "memoryUsage":
|
||||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
return `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) 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":
|
case "pvc":
|
||||||
return {
|
switch (queryName) {
|
||||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
case "diskUsage":
|
||||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
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":
|
case "ingress":
|
||||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
switch (queryName) {
|
||||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
case "bytesSentSuccess":
|
||||||
|
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||||
return {
|
case "bytesSentFailure":
|
||||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
case "requestDurationSeconds":
|
||||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
return `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)`
|
case "responseDurationSeconds":
|
||||||
};
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* 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, V1Service } from "@kubernetes/client-node";
|
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
import { inspect } from "util";
|
||||||
|
|
||||||
export class PrometheusOperator implements PrometheusProvider {
|
export class PrometheusOperator extends PrometheusProvider {
|
||||||
rateAccuracy = "1m";
|
readonly rateAccuracy: string = "1m";
|
||||||
id = "operator";
|
readonly id: string = "operator";
|
||||||
name = "Prometheus Operator";
|
readonly name: string = "Prometheus Operator";
|
||||||
|
readonly isConfigurable: boolean = false;
|
||||||
|
|
||||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void> {
|
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined> {
|
||||||
try {
|
return this.getFirstNamespacedServer(client, "operated-prometheus=true", "self-monitor=true");
|
||||||
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 getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void {
|
public getQuery(opts: Record<string, string>, queryName: string): string {
|
||||||
switch(opts.category) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
return {
|
switch (queryName) {
|
||||||
memoryUsage: `
|
case "memoryUsage":
|
||||||
sum(
|
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}"}`);
|
||||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
case "memoryRequests":
|
||||||
)
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`;
|
||||||
`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`),
|
case "memoryLimits":
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`;
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`,
|
case "memoryCapacity":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`,
|
return `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}"})`,
|
case "cpuUsage":
|
||||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`,
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`,
|
case "cpuRequests":
|
||||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`,
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
||||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", node=~"${opts.nodes}"})`,
|
case "cpuLimits":
|
||||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
|
case "cpuCapacity":
|
||||||
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}"})`
|
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":
|
case "nodes":
|
||||||
return {
|
switch (queryName) {
|
||||||
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)`,
|
case "memoryUsage":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
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)`;
|
||||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
|
case "memoryCapacity":
|
||||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
|
case "cpuUsage":
|
||||||
fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`
|
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":
|
case "pods":
|
||||||
return {
|
switch (queryName) {
|
||||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
case "cpuUsage":
|
||||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuRequests":
|
||||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuLimits":
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "memoryUsage":
|
||||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
return `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) 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":
|
case "pvc":
|
||||||
return {
|
switch (queryName) {
|
||||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
case "diskUsage":
|
||||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
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":
|
case "ingress":
|
||||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
switch (queryName) {
|
||||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
case "bytesSentSuccess":
|
||||||
|
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||||
return {
|
case "bytesSentFailure":
|
||||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
case "requestDurationSeconds":
|
||||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
return `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)`
|
case "responseDurationSeconds":
|
||||||
};
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
|
import { Singleton } from "../../common/utils";
|
||||||
export type PrometheusClusterQuery = {
|
import logger from "../logger";
|
||||||
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;
|
|
||||||
|
|
||||||
export type PrometheusService = {
|
export type PrometheusService = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -80,33 +30,61 @@ export type PrometheusService = {
|
|||||||
port: number;
|
port: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PrometheusProvider {
|
export abstract class PrometheusProvider {
|
||||||
id: string;
|
abstract readonly id: string;
|
||||||
name: string;
|
abstract readonly name: string;
|
||||||
getQueries(opts: PrometheusQueryOpts): PrometheusQuery | void;
|
abstract readonly rateAccuracy: string;
|
||||||
getPrometheusService(client: CoreV1Api): Promise<PrometheusService | void>;
|
abstract readonly isConfigurable: boolean;
|
||||||
}
|
|
||||||
|
|
||||||
export type PrometheusProviderList = {
|
abstract getQuery(opts: Record<string, string>, queryName: string): string;
|
||||||
[key: string]: PrometheusProvider;
|
abstract getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined>;
|
||||||
};
|
|
||||||
|
|
||||||
export class PrometheusProviderRegistry {
|
protected bytesSent(ingress: string, namespace: string, statuses: string): string {
|
||||||
private static prometheusProviders: PrometheusProviderList = {};
|
return `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||||
|
}
|
||||||
|
|
||||||
static getProvider(type: string): PrometheusProvider {
|
protected async getFirstNamespacedServer(client: CoreV1Api, ...selectors: string[]): Promise<PrometheusService | undefined> {
|
||||||
if (!this.prometheusProviders[type]) {
|
try {
|
||||||
throw "Unknown Prometheus provider";
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`${this.name}: failed to list services: ${error.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prometheusProviders[type];
|
return undefined;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
static registerProvider(key: string, provider: PrometheusProvider) {
|
|
||||||
this.prometheusProviders[key] = provider;
|
export class PrometheusProviderRegistry extends Singleton {
|
||||||
}
|
public providers = new Map<string, PrometheusProvider>();
|
||||||
|
|
||||||
static getProviders(): PrometheusProvider[] {
|
getByKind(kind: string): PrometheusProvider {
|
||||||
return Object.values(this.prometheusProviders);
|
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.
|
* 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 type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
import { inspect } from "util";
|
||||||
|
|
||||||
export class PrometheusStacklight implements PrometheusProvider {
|
export class PrometheusStacklight extends PrometheusProvider {
|
||||||
id = "stacklight";
|
readonly id: string = "stacklight";
|
||||||
name = "Stacklight";
|
readonly name: string = "Stacklight";
|
||||||
rateAccuracy = "1m";
|
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 {
|
try {
|
||||||
const resp = await client.readNamespacedService("prometheus-server", "stacklight");
|
const resp = await client.readNamespacedService("prometheus-server", "stacklight");
|
||||||
const service = resp.body;
|
const service = resp.body;
|
||||||
@ -41,66 +43,101 @@ export class PrometheusStacklight implements PrometheusProvider {
|
|||||||
};
|
};
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`);
|
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) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
return {
|
switch (queryName) {
|
||||||
memoryUsage: `
|
case "memoryUsage":
|
||||||
sum(
|
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}"}`);
|
||||||
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
|
case "memoryRequests":
|
||||||
) by (kubernetes_name)
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`),
|
case "memoryLimits":
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
case "memoryCapacity":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
|
return `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}]))`,
|
case "cpuUsage":
|
||||||
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
case "cpuRequests":
|
||||||
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
podUsage: `sum({__name__=~"kubelet_running_pod_count|kubelet_running_pods", instance=~"${opts.nodes}"})`,
|
case "cpuLimits":
|
||||||
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
|
return `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`,
|
case "cpuCapacity":
|
||||||
fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`
|
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":
|
case "nodes":
|
||||||
return {
|
switch (queryName) {
|
||||||
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`,
|
case "memoryUsage":
|
||||||
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
|
return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`;
|
||||||
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`,
|
case "memoryCapacity":
|
||||||
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
|
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||||
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`,
|
case "cpuUsage":
|
||||||
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`
|
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":
|
case "pods":
|
||||||
return {
|
switch (queryName) {
|
||||||
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
case "cpuUsage":
|
||||||
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
||||||
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuRequests":
|
||||||
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "cpuLimits":
|
||||||
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
return `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
|
case "memoryUsage":
|
||||||
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
|
return `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) 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":
|
case "pvc":
|
||||||
return {
|
switch (queryName) {
|
||||||
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`,
|
case "diskUsage":
|
||||||
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`
|
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":
|
case "ingress":
|
||||||
const bytesSent = (ingress: string, namespace: string, statuses: string) =>
|
switch (queryName) {
|
||||||
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
case "bytesSentSuccess":
|
||||||
|
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
||||||
return {
|
case "bytesSentFailure":
|
||||||
bytesSentSuccess: bytesSent(opts.ingress, opts.namespace, "^2\\\\d*"),
|
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
||||||
bytesSentFailure: bytesSent(opts.ingress, opts.namespace, "^5\\\\d*"),
|
case "requestDurationSeconds":
|
||||||
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`,
|
return `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)`
|
case "responseDurationSeconds":
|
||||||
};
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Metrics API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
||||||
|
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||||
|
|
||||||
// Port-forward API
|
// Port-forward API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, PortForwardRoute.routePortForward);
|
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 type { ClusterPrometheusMetadata } from "../../common/cluster-store";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { getMetrics } from "../k8s-request";
|
import { getMetrics } from "../k8s-request";
|
||||||
|
import { PrometheusProviderRegistry } from "../prometheus";
|
||||||
|
|
||||||
export type IMetricsQuery = string | string[] | {
|
export type IMetricsQuery = string | string[] | {
|
||||||
[metricName: string]: string;
|
[metricName: string]: string;
|
||||||
@ -62,6 +63,12 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
|
|||||||
return Promise.all(queries.map(loadMetric));
|
return Promise.all(queries.map(loadMetric));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MetricProviderInfo {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
isConfigurable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class MetricsRoute {
|
export class MetricsRoute {
|
||||||
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
||||||
@ -92,9 +99,10 @@ export class MetricsRoute {
|
|||||||
|
|
||||||
respondJson(response, data);
|
respondJson(response, data);
|
||||||
} else {
|
} else {
|
||||||
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
|
const queries = Object.entries<Record<string, string>>(payload)
|
||||||
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
|
.map(([queryName, queryOpts]) => (
|
||||||
));
|
prometheusProvider.getQuery(queryOpts, queryName)
|
||||||
|
));
|
||||||
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
||||||
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
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;
|
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][];
|
values: [number, string][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetricProviderInfo {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
isConfigurable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IMetricsReqParams {
|
export interface IMetricsReqParams {
|
||||||
start?: number | string; // timestamp in seconds or valid date-string
|
start?: number | string; // timestamp in seconds or valid date-string
|
||||||
end?: number | 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 {
|
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||||
|
|||||||
@ -21,18 +21,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { prometheusProviders } from "../../../../common/prometheus-providers";
|
|
||||||
import type { Cluster } from "../../../../main/cluster";
|
import type { Cluster } from "../../../../main/cluster";
|
||||||
import { SubTitle } from "../../layout/sub-title";
|
import { SubTitle } from "../../layout/sub-title";
|
||||||
import { Select, SelectOption } from "../../select";
|
import { Select, SelectOption } from "../../select";
|
||||||
import { Input } from "../../input";
|
import { Input } from "../../input";
|
||||||
import { observable, computed, autorun, makeObservable } from "mobx";
|
import { observable, computed, autorun, makeObservable } from "mobx";
|
||||||
import { productName } from "../../../../common/vars";
|
import { productName } from "../../../../common/vars";
|
||||||
|
import { MetricProviderInfo, metricsApi } from "../../../api/endpoints/metrics.api";
|
||||||
const options: SelectOption<string>[] = [
|
import { Spinner } from "../../spinner";
|
||||||
{ value: "", label: "Auto detect" },
|
|
||||||
...prometheusProviders.map(pp => ({value: pp.id, label: pp.name}))
|
|
||||||
];
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
@ -42,16 +38,27 @@ interface Props {
|
|||||||
export class ClusterPrometheusSetting extends React.Component<Props> {
|
export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||||
@observable path = "";
|
@observable path = "";
|
||||||
@observable provider = "";
|
@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) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get canEditPrometheusPath() {
|
@computed get canEditPrometheusPath(): boolean {
|
||||||
if (this.provider === "" || this.provider === "lens") return false;
|
return Boolean(
|
||||||
|
this.loadedOptions
|
||||||
return true;
|
.find(opt => opt.id === this.provider)
|
||||||
|
?.isConfigurable
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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 = () => {
|
parsePrometheusPath = () => {
|
||||||
@ -110,15 +127,21 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<section>
|
<section>
|
||||||
<SubTitle title="Prometheus"/>
|
<SubTitle title="Prometheus"/>
|
||||||
<Select
|
{
|
||||||
value={this.provider}
|
this.loading
|
||||||
onChange={({value}) => {
|
? <Spinner />
|
||||||
this.provider = value;
|
: <>
|
||||||
this.onSaveProvider();
|
<Select
|
||||||
}}
|
value={this.provider}
|
||||||
options={options}
|
onChange={({ value }) => {
|
||||||
/>
|
this.provider = value;
|
||||||
<small className="hint">What query format is used to fetch metrics from Prometheus</small>
|
this.onSaveProvider();
|
||||||
|
}}
|
||||||
|
options={this.options}
|
||||||
|
/>
|
||||||
|
<small className="hint">What query format is used to fetch metrics from Prometheus</small>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
{this.canEditPrometheusPath && (
|
{this.canEditPrometheusPath && (
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user