mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
WIP: status page - API and UI (#6243)
* status page initial commit Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * refactor useFetch Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * refactoring Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * adding tests Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * snapshot testing Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * fix wrong go files formatting Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * change the snapshot library Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * update api paths Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * move test folder outside src Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * useFetches tests Signed-off-by: blalov <boyko.lalov@tick42.com> * sticky navbar Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * handle runtimeInfo error on Gather() and add json tags to RuntimeInfo struct Signed-off-by: blalov <boyko.lalov@tick42.com> * refactor alert managers section Signed-off-by: blalov <boyko.lalov@tick42.com>
This commit is contained in:
parent
ca9fce46a3
commit
cb7cbad5f9
|
@ -108,6 +108,32 @@ type rulesRetriever interface {
|
|||
AlertingRules() []*rules.AlertingRule
|
||||
}
|
||||
|
||||
// PrometheusVersion contains build information about Prometheus.
|
||||
type PrometheusVersion struct {
|
||||
Version string `json:"version"`
|
||||
Revision string `json:"revision"`
|
||||
Branch string `json:"branch"`
|
||||
BuildUser string `json:"buildUser"`
|
||||
BuildDate string `json:"buildDate"`
|
||||
GoVersion string `json:"goVersion"`
|
||||
}
|
||||
|
||||
// RuntimeInfo contains runtime information about Prometheus.
|
||||
type RuntimeInfo struct {
|
||||
StartTime time.Time `json:"startTime"`
|
||||
CWD string `json:"CWD"`
|
||||
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
|
||||
LastConfigTime time.Time `json:"lastConfigTime"`
|
||||
ChunkCount int64 `json:"chunkCount"`
|
||||
TimeSeriesCount int64 `json:"timeSeriesCount"`
|
||||
CorruptionCount int64 `json:"corruptionCount"`
|
||||
GoroutineCount int `json:"goroutineCount"`
|
||||
GOMAXPROCS int `json:"GOMAXPROCS"`
|
||||
GOGC string `json:"GOGC"`
|
||||
GODEBUG string `json:"GODEBUG"`
|
||||
StorageRetention string `json:"storageRetention"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Status status `json:"status"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
|
@ -154,6 +180,8 @@ type API struct {
|
|||
remoteReadMaxBytesInFrame int
|
||||
remoteReadGate *gate.Gate
|
||||
CORSOrigin *regexp.Regexp
|
||||
buildInfo *PrometheusVersion
|
||||
runtimeInfo func() (RuntimeInfo, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -178,6 +206,8 @@ func NewAPI(
|
|||
remoteReadConcurrencyLimit int,
|
||||
remoteReadMaxBytesInFrame int,
|
||||
CORSOrigin *regexp.Regexp,
|
||||
runtimeInfo func() (RuntimeInfo, error),
|
||||
buildInfo *PrometheusVersion,
|
||||
) *API {
|
||||
return &API{
|
||||
QueryEngine: qe,
|
||||
|
@ -197,6 +227,8 @@ func NewAPI(
|
|||
remoteReadMaxBytesInFrame: remoteReadMaxBytesInFrame,
|
||||
logger: logger,
|
||||
CORSOrigin: CORSOrigin,
|
||||
runtimeInfo: runtimeInfo,
|
||||
buildInfo: buildInfo,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,6 +274,8 @@ func (api *API) Register(r *route.Router) {
|
|||
r.Get("/alertmanagers", wrap(api.alertmanagers))
|
||||
|
||||
r.Get("/status/config", wrap(api.serveConfig))
|
||||
r.Get("/status/runtimeinfo", wrap(api.serveRuntimeInfo))
|
||||
r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
|
||||
r.Get("/status/flags", wrap(api.serveFlags))
|
||||
r.Post("/read", api.ready(http.HandlerFunc(api.remoteRead)))
|
||||
|
||||
|
@ -832,6 +866,18 @@ type prometheusConfig struct {
|
|||
YAML string `json:"yaml"`
|
||||
}
|
||||
|
||||
func (api *API) serveRuntimeInfo(r *http.Request) apiFuncResult {
|
||||
status, err := api.runtimeInfo()
|
||||
if err != nil {
|
||||
return apiFuncResult{status, &apiError{errorInternal, err}, nil, nil}
|
||||
}
|
||||
return apiFuncResult{status, nil, nil, nil}
|
||||
}
|
||||
|
||||
func (api *API) serveBuildInfo(r *http.Request) apiFuncResult {
|
||||
return apiFuncResult{api.buildInfo, nil, nil, nil}
|
||||
}
|
||||
|
||||
func (api *API) serveConfig(r *http.Request) apiFuncResult {
|
||||
cfg := &prometheusConfig{
|
||||
YAML: api.config().String(),
|
||||
|
@ -1176,6 +1222,7 @@ func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data inter
|
|||
Error: apiErr.err.Error(),
|
||||
Data: data,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
level.Error(api.logger).Log("msg", "error marshaling json response", "err", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"@fortawesome/free-solid-svg-icons": "^5.7.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"@reach/router": "^1.2.1",
|
||||
"@testing-library/react-hooks": "^3.1.1",
|
||||
"@types/jest": "^24.0.20",
|
||||
"@types/jquery": "^3.3.29",
|
||||
"@types/node": "^12.11.1",
|
||||
|
@ -18,9 +19,11 @@
|
|||
"@types/sanitize-html": "^1.20.2",
|
||||
"bootstrap": "^4.2.1",
|
||||
"downshift": "^3.2.2",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"flot": "^3.2.13",
|
||||
"fuzzy": "^0.1.3",
|
||||
"i": "^0.3.6",
|
||||
"jest-fetch-mock": "^2.1.2",
|
||||
"jquery": "^3.3.1",
|
||||
"jquery.flot.tooltip": "^0.9.0",
|
||||
"jsdom": "^15.2.0",
|
||||
|
@ -32,6 +35,7 @@
|
|||
"react-dom": "^16.7.0",
|
||||
"react-resize-detector": "^4.2.1",
|
||||
"react-scripts": "^3.2.0",
|
||||
"react-test-renderer": "^16.9.0",
|
||||
"reactstrap": "^8.0.1",
|
||||
"sanitize-html": "^1.20.1",
|
||||
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||
|
@ -83,5 +87,10 @@
|
|||
"prettier": "^1.18.2",
|
||||
"sinon": "^7.5.0"
|
||||
},
|
||||
"proxy": "http://localhost:9090"
|
||||
"proxy": "http://localhost:9090",
|
||||
"jest": {
|
||||
"snapshotSerializers": [
|
||||
"enzyme-to-json/serializer"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ input[type='checkbox']:checked + label {
|
|||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.capitalize-title::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.expression-input {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class App extends Component {
|
|||
return (
|
||||
<>
|
||||
<Navigation />
|
||||
<Container fluid>
|
||||
<Container fluid style={{ paddingTop: 70 }}>
|
||||
<Router basepath="/new">
|
||||
<PanelList path="/graph" />
|
||||
<Alerts path="/alerts" />
|
||||
|
|
|
@ -34,7 +34,7 @@ class Graph extends PureComponent<GraphProps> {
|
|||
private chartRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
renderLabels(labels: { [key: string]: string }) {
|
||||
let labelStrings: string[] = [];
|
||||
const labelStrings: string[] = [];
|
||||
for (const label in labels) {
|
||||
if (label !== '__name__') {
|
||||
labelStrings.push('<strong>' + label + '</strong>: ' + escapeHTML(labels[label]));
|
||||
|
|
|
@ -17,7 +17,7 @@ const Navigation = () => {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const toggle = () => setIsOpen(!isOpen);
|
||||
return (
|
||||
<Navbar className="mb-3" dark color="dark" expand="md">
|
||||
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
||||
<NavbarToggler onClick={toggle} />
|
||||
<Link className="pt-0 navbar-brand" to="/new/graph">
|
||||
Prometheus
|
||||
|
|
55
web/ui/react-app/src/hooks/useFetches.test.tsx
Normal file
55
web/ui/react-app/src/hooks/useFetches.test.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import useFetches from './useFetches';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
describe('useFetches', () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.resetMocks();
|
||||
});
|
||||
it('should can handle multiple requests', async done => {
|
||||
fetchMock.mockResponse(JSON.stringify({ satus: 'success', data: { id: 1 } }));
|
||||
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar', '/foo/bar', '/foo/bar'] });
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.response).toHaveLength(3);
|
||||
done();
|
||||
});
|
||||
it('should can handle success flow -> isLoading=true, response=[data, data], isLoading=false', async done => {
|
||||
fetchMock.mockResponse(JSON.stringify({ satus: 'success', data: { id: 1 } }));
|
||||
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||
expect(result.current.isLoading).toEqual(true);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.response).toHaveLength(1);
|
||||
expect(result.current.isLoading).toEqual(false);
|
||||
done();
|
||||
});
|
||||
it('should isLoading remains true on empty response', async done => {
|
||||
fetchMock.mockResponse(jest.fn());
|
||||
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||
expect(result.current.isLoading).toEqual(true);
|
||||
await waitForNextUpdate();
|
||||
setTimeout(() => {
|
||||
expect(result.current.isLoading).toEqual(true);
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
it('should set error message when response fail', async done => {
|
||||
fetchMock.mockReject(new Error('errr'));
|
||||
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||
expect(result.current.isLoading).toEqual(true);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.error!.message).toEqual('errr');
|
||||
expect(result.current.isLoading).toEqual(true);
|
||||
done();
|
||||
});
|
||||
it('should throw an error if array is empty', async done => {
|
||||
try {
|
||||
useFetches([]);
|
||||
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: [] });
|
||||
await waitForNextUpdate().then(done);
|
||||
expect(result.error.message).toEqual("Doesn't have url to fetch.");
|
||||
done();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
36
web/ui/react-app/src/hooks/useFetches.tsx
Normal file
36
web/ui/react-app/src/hooks/useFetches.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
const useFetches = <R extends any>(urls: string[], options?: RequestInit) => {
|
||||
if (!urls.length) {
|
||||
throw new Error("Doesn't have url to fetch.");
|
||||
}
|
||||
const [response, setResponse] = useState<R[]>();
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const responses: R[] = await Promise.all(
|
||||
urls
|
||||
.map(async url => {
|
||||
const res = await fetch(url, options);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const result = await res.json();
|
||||
return result.data;
|
||||
})
|
||||
.filter(Boolean) // Remove falsy values
|
||||
);
|
||||
setResponse(responses);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [urls, options]);
|
||||
|
||||
return { response, error, isLoading: !response || !response.length };
|
||||
};
|
||||
|
||||
export default useFetches;
|
72
web/ui/react-app/src/pages/Status.test.tsx
Normal file
72
web/ui/react-app/src/pages/Status.test.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
import * as React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Status } from '.';
|
||||
import { Alert } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as useFetch from '../hooks/useFetches';
|
||||
import toJson from 'enzyme-to-json';
|
||||
|
||||
describe('Status', () => {
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
it('should render spinner while waiting data', () => {
|
||||
const wrapper = shallow(<Status />);
|
||||
expect(wrapper.find(FontAwesomeIcon)).toHaveLength(1);
|
||||
});
|
||||
it('should render Alert on error', () => {
|
||||
(useFetch as any).default = jest.fn().mockImplementation(() => ({ error: new Error('foo') }));
|
||||
const wrapper = shallow(<Status />);
|
||||
expect(wrapper.find(Alert)).toHaveLength(1);
|
||||
});
|
||||
it('should fetch proper API endpoints', () => {
|
||||
const useFetchSpy = jest.spyOn(useFetch, 'default');
|
||||
shallow(<Status />);
|
||||
expect(useFetchSpy).toHaveBeenCalledWith([
|
||||
'../api/v1/status/runtimeinfo',
|
||||
'../api/v1/status/buildinfo',
|
||||
'../api/v1/alertmanagers',
|
||||
]);
|
||||
});
|
||||
describe('Snapshot testing', () => {
|
||||
const response = [
|
||||
{
|
||||
startTime: '2019-10-30T22:03:23.247913868+02:00',
|
||||
CWD: '/home/boyskila/Desktop/prometheus',
|
||||
reloadConfigSuccess: true,
|
||||
lastConfigTime: '2019-10-30T22:03:23+02:00',
|
||||
chunkCount: 1383,
|
||||
timeSeriesCount: 461,
|
||||
corruptionCount: 0,
|
||||
goroutineCount: 37,
|
||||
GOMAXPROCS: 4,
|
||||
GOGC: '',
|
||||
GODEBUG: '',
|
||||
storageRetention: '15d',
|
||||
},
|
||||
{
|
||||
version: '',
|
||||
revision: '',
|
||||
branch: '',
|
||||
buildUser: '',
|
||||
buildDate: '',
|
||||
goVersion: 'go1.13.3',
|
||||
},
|
||||
{
|
||||
activeAlertmanagers: [
|
||||
{ url: 'https://1.2.3.4:9093/api/v1/alerts' },
|
||||
{ url: 'https://1.2.3.5:9093/api/v1/alerts' },
|
||||
{ url: 'https://1.2.3.6:9093/api/v1/alerts' },
|
||||
{ url: 'https://1.2.3.7:9093/api/v1/alerts' },
|
||||
{ url: 'https://1.2.3.8:9093/api/v1/alerts' },
|
||||
{ url: 'https://1.2.3.9:9093/api/v1/alerts' },
|
||||
],
|
||||
droppedAlertmanagers: [],
|
||||
},
|
||||
];
|
||||
it('should match table snapshot', () => {
|
||||
(useFetch as any).default = jest.fn().mockImplementation(() => ({ response }));
|
||||
const wrapper = shallow(<Status />);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,108 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { Table, Alert } from 'reactstrap';
|
||||
import useFetches from '../hooks/useFetches';
|
||||
|
||||
const Status: FC<RouteComponentProps> = () => <div>Status page</div>;
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Status;
|
||||
const ENDPOINTS = ['../api/v1/status/runtimeinfo', '../api/v1/status/buildinfo', '../api/v1/alertmanagers'];
|
||||
const sectionTitles = ['Runtime Information', 'Build Information', 'Alertmanagers'];
|
||||
|
||||
interface StatusConfig {
|
||||
[k: string]: { title?: string; customizeValue?: (v: any) => any; customRow?: boolean; skip?: boolean };
|
||||
}
|
||||
|
||||
type StatusPageState = Array<{ [k: string]: string }>;
|
||||
|
||||
export const statusConfig: StatusConfig = {
|
||||
startTime: { title: 'Start time', customizeValue: (v: string) => new Date(v).toUTCString() },
|
||||
CWD: { title: 'Working directory' },
|
||||
reloadConfigSuccess: {
|
||||
title: 'Configuration reload',
|
||||
customizeValue: (v: boolean) => (v ? 'Successful' : 'Unsuccessful'),
|
||||
},
|
||||
lastConfigTime: { title: 'Last successful configuration reload' },
|
||||
chunkCount: { title: 'Head chunks' },
|
||||
timeSeriesCount: { title: 'Head time series' },
|
||||
corruptionCount: { title: 'WAL corruptions' },
|
||||
goroutineCount: { title: 'Goroutines' },
|
||||
storageRetention: { title: 'Storage retention' },
|
||||
activeAlertmanagers: {
|
||||
customRow: true,
|
||||
customizeValue: (alertMgrs: { url: string }[]) => {
|
||||
return (
|
||||
<Fragment key="alert-managers">
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
</tr>
|
||||
{alertMgrs.map(({ url }) => {
|
||||
const { origin, pathname } = new URL(url);
|
||||
return (
|
||||
<tr key={url}>
|
||||
<td>
|
||||
<a href={url}>{origin}</a>
|
||||
{pathname}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
droppedAlertmanagers: { skip: true },
|
||||
};
|
||||
|
||||
const Status = () => {
|
||||
const { response: data, error, isLoading } = useFetches<StatusPageState[]>(ENDPOINTS);
|
||||
if (error) {
|
||||
return (
|
||||
<Alert color="danger">
|
||||
<strong>Error:</strong> Error fetching status: {error.message}
|
||||
</Alert>
|
||||
);
|
||||
} else if (isLoading) {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
size="3x"
|
||||
icon={faSpinner}
|
||||
spin
|
||||
className="position-absolute"
|
||||
style={{ transform: 'translate(-50%, -50%)', top: '50%', left: '50%' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return data
|
||||
? data.map((statuses, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<h2>{sectionTitles[i]}</h2>
|
||||
<Table className="h-auto" size="sm" bordered striped>
|
||||
<tbody>
|
||||
{Object.entries(statuses).map(([k, v]) => {
|
||||
const { title = k, customizeValue = (val: any) => val, customRow, skip } = statusConfig[k] || {};
|
||||
if (skip) {
|
||||
return null;
|
||||
}
|
||||
if (customRow) {
|
||||
return customizeValue(v);
|
||||
}
|
||||
return (
|
||||
<tr key={k}>
|
||||
<th className="capitalize-title" style={{ width: '35%' }}>
|
||||
{title}
|
||||
</th>
|
||||
<td className="text-break">{customizeValue(v)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Fragment>
|
||||
);
|
||||
})
|
||||
: null;
|
||||
};
|
||||
|
||||
export default Status as FC<RouteComponentProps>;
|
||||
|
|
465
web/ui/react-app/src/pages/__snapshots__/Status.test.tsx.snap
Normal file
465
web/ui/react-app/src/pages/__snapshots__/Status.test.tsx.snap
Normal file
|
@ -0,0 +1,465 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Status Snapshot testing should match table snapshot 1`] = `
|
||||
Array [
|
||||
<Fragment
|
||||
key="0"
|
||||
>
|
||||
<h2>
|
||||
Runtime Information
|
||||
</h2>
|
||||
<Table
|
||||
bordered={true}
|
||||
className="h-auto"
|
||||
responsiveTag="div"
|
||||
size="sm"
|
||||
striped={true}
|
||||
tag="table"
|
||||
>
|
||||
<tbody>
|
||||
<tr
|
||||
key="startTime"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Start time
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
Wed, 30 Oct 2019 20:03:23 GMT
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="CWD"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Working directory
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
/home/boyskila/Desktop/prometheus
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="reloadConfigSuccess"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Configuration reload
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
Successful
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="lastConfigTime"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Last successful configuration reload
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
2019-10-30T22:03:23+02:00
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="chunkCount"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Head chunks
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
1383
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="timeSeriesCount"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Head time series
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
461
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="corruptionCount"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
WAL corruptions
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
0
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="goroutineCount"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Goroutines
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
37
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="GOMAXPROCS"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
GOMAXPROCS
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
4
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="GOGC"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
GOGC
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="GODEBUG"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
GODEBUG
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="storageRetention"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Storage retention
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
15d
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Fragment>,
|
||||
<Fragment
|
||||
key="1"
|
||||
>
|
||||
<h2>
|
||||
Build Information
|
||||
</h2>
|
||||
<Table
|
||||
bordered={true}
|
||||
className="h-auto"
|
||||
responsiveTag="div"
|
||||
size="sm"
|
||||
striped={true}
|
||||
tag="table"
|
||||
>
|
||||
<tbody>
|
||||
<tr
|
||||
key="version"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
version
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="revision"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
revision
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="branch"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
branch
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="buildUser"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
buildUser
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="buildDate"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
buildDate
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
key="goVersion"
|
||||
>
|
||||
<th
|
||||
className="capitalize-title"
|
||||
style={
|
||||
Object {
|
||||
"width": "35%",
|
||||
}
|
||||
}
|
||||
>
|
||||
goVersion
|
||||
</th>
|
||||
<td
|
||||
className="text-break"
|
||||
>
|
||||
go1.13.3
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Fragment>,
|
||||
<Fragment
|
||||
key="2"
|
||||
>
|
||||
<h2>
|
||||
Alertmanagers
|
||||
</h2>
|
||||
<Table
|
||||
bordered={true}
|
||||
className="h-auto"
|
||||
responsiveTag="div"
|
||||
size="sm"
|
||||
striped={true}
|
||||
tag="table"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Endpoint
|
||||
</th>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.4:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.4:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.4:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.5:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.5:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.5:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.6:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.6:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.6:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.7:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.7:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.7:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.8:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.8:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.8:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="https://1.2.3.9:9093/api/v1/alerts"
|
||||
>
|
||||
<td>
|
||||
<a
|
||||
href="https://1.2.3.9:9093/api/v1/alerts"
|
||||
>
|
||||
https://1.2.3.9:9093
|
||||
</a>
|
||||
/api/v1/alerts
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Fragment>,
|
||||
]
|
||||
`;
|
|
@ -20,6 +20,6 @@
|
|||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
"src", "test"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -857,7 +857,7 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.4":
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f"
|
||||
integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==
|
||||
|
@ -1282,6 +1282,14 @@
|
|||
"@svgr/plugin-svgo" "^4.3.1"
|
||||
loader-utils "^1.2.3"
|
||||
|
||||
"@testing-library/react-hooks@^3.1.1":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.1.1.tgz#5c93e463c0252bea6ac237ec8d9c982c27d67208"
|
||||
integrity sha512-HANnmA68/i6RwZn9j7pcbAg438PoDToftRQ1CH0j893WuQGtENFm57GKTagtmXXDN5gKh3rVbN1GH6HDvHbk6A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.4"
|
||||
"@types/testing-library__react-hooks" "^2.0.0"
|
||||
|
||||
"@types/babel__core@^7.1.0":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
|
||||
|
@ -1470,6 +1478,13 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-test-renderer@*":
|
||||
version "16.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4"
|
||||
integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^16.8.2":
|
||||
version "16.9.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.9.tgz#a62c6f40f04bc7681be5e20975503a64fe783c3a"
|
||||
|
@ -1508,6 +1523,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
|
||||
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
|
||||
|
||||
"@types/testing-library__react-hooks@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-2.0.0.tgz#7b289d64945517ae8ba9cbcb0c5b282432aaeffa"
|
||||
integrity sha512-YUVqXGCChJKEJ4aAnMXqPCq0NfPAFVsJeGIb2y/iiMjxwyu+45+vR+AHOwjJHHKEHeC0ZhOGrZ5gSEmaJe4tyQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-test-renderer" "*"
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "13.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
|
||||
|
@ -3968,6 +3991,13 @@ enzyme-shallow-equal@^1.0.0:
|
|||
has "^1.0.3"
|
||||
object-is "^1.0.1"
|
||||
|
||||
enzyme-to-json@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.4.3.tgz#ed4386f48768ed29e2d1a2910893542c34e7e0af"
|
||||
integrity sha512-jqNEZlHqLdz7OTpXSzzghArSS3vigj67IU/fWkPyl1c0TCj9P5s6Ze0kRkYZWNEoCqCR79xlQbigYlMx5erh8A==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
enzyme@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6"
|
||||
|
@ -9104,7 +9134,7 @@ react-scripts@^3.2.0:
|
|||
optionalDependencies:
|
||||
fsevents "2.0.7"
|
||||
|
||||
react-test-renderer@^16.0.0-0:
|
||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0:
|
||||
version "16.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8"
|
||||
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==
|
||||
|
|
56
web/web.go
56
web/web.go
|
@ -163,6 +163,9 @@ func (m *metrics) instrumentHandler(handlerName string, handler http.HandlerFunc
|
|||
)
|
||||
}
|
||||
|
||||
// PrometheusVersion contains build information about Prometheus.
|
||||
type PrometheusVersion = api_v1.PrometheusVersion
|
||||
|
||||
// Handler serves various HTTP endpoints of the Prometheus server
|
||||
type Handler struct {
|
||||
logger log.Logger
|
||||
|
@ -206,16 +209,6 @@ func (h *Handler) ApplyConfig(conf *config.Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PrometheusVersion contains build information about Prometheus.
|
||||
type PrometheusVersion struct {
|
||||
Version string `json:"version"`
|
||||
Revision string `json:"revision"`
|
||||
Branch string `json:"branch"`
|
||||
BuildUser string `json:"buildUser"`
|
||||
BuildDate string `json:"buildDate"`
|
||||
GoVersion string `json:"goVersion"`
|
||||
}
|
||||
|
||||
// Options for the web Handler.
|
||||
type Options struct {
|
||||
Context context.Context
|
||||
|
@ -310,6 +303,8 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
h.options.RemoteReadConcurrencyLimit,
|
||||
h.options.RemoteReadBytesInFrame,
|
||||
h.options.CORSOrigin,
|
||||
h.runtimeInfo,
|
||||
h.versionInfo,
|
||||
)
|
||||
|
||||
if o.RoutePrefix != "/" {
|
||||
|
@ -744,6 +739,47 @@ func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
|
|||
h.executeTemplate(w, "status.html", status)
|
||||
}
|
||||
|
||||
func (h *Handler) runtimeInfo() (api_v1.RuntimeInfo, error) {
|
||||
status := api_v1.RuntimeInfo{
|
||||
StartTime: h.birth,
|
||||
CWD: h.cwd,
|
||||
GoroutineCount: runtime.NumGoroutine(),
|
||||
GOMAXPROCS: runtime.GOMAXPROCS(0),
|
||||
GOGC: os.Getenv("GOGC"),
|
||||
GODEBUG: os.Getenv("GODEBUG"),
|
||||
}
|
||||
|
||||
if h.options.TSDBCfg.RetentionDuration != 0 {
|
||||
status.StorageRetention = h.options.TSDBCfg.RetentionDuration.String()
|
||||
}
|
||||
if h.options.TSDBCfg.MaxBytes != 0 {
|
||||
if status.StorageRetention != "" {
|
||||
status.StorageRetention = status.StorageRetention + " or "
|
||||
}
|
||||
status.StorageRetention = status.StorageRetention + h.options.TSDBCfg.MaxBytes.String()
|
||||
}
|
||||
|
||||
metrics, err := h.gatherer.Gather()
|
||||
if err != nil {
|
||||
return status, errors.Errorf("error gathering runtime status: %s", err)
|
||||
}
|
||||
for _, mF := range metrics {
|
||||
switch *mF.Name {
|
||||
case "prometheus_tsdb_head_chunks":
|
||||
status.ChunkCount = int64(toFloat64(mF))
|
||||
case "prometheus_tsdb_head_series":
|
||||
status.TimeSeriesCount = int64(toFloat64(mF))
|
||||
case "prometheus_tsdb_wal_corruptions_total":
|
||||
status.CorruptionCount = int64(toFloat64(mF))
|
||||
case "prometheus_config_last_reload_successful":
|
||||
status.ReloadConfigSuccess = toFloat64(mF) != 0
|
||||
case "prometheus_config_last_reload_success_timestamp_seconds":
|
||||
status.LastConfigTime = time.Unix(int64(toFloat64(mF)), 0)
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func toFloat64(f *io_prometheus_client.MetricFamily) float64 {
|
||||
m := *f.Metric[0]
|
||||
if m.Gauge != nil {
|
||||
|
|
Loading…
Reference in a new issue