mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-11 03:54:28 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f7f56acf | ||
|
|
e6b3a6c9db | ||
|
|
d6884cacdb | ||
|
|
bb155d2356 | ||
|
|
c49ffd83a3 | ||
|
|
8a5178f0dc | ||
|
|
057fba112b | ||
|
|
4c0b81b5c7 | ||
|
|
1d2f964a63 | ||
|
|
171fd714de | ||
|
|
789880fa07 | ||
|
|
f25edf1e29 | ||
|
|
c6e2e297d5 | ||
|
|
2bc01e50bd | ||
|
|
38770809e3 | ||
|
|
9dd9f1133b | ||
|
|
8b743f7803 | ||
|
|
868b7691b3 | ||
|
|
613e6ba5f9 | ||
|
|
8fe2fe5aec | ||
|
|
55a7b5b1b3 | ||
|
|
8b0abbec79 | ||
|
|
24416ceabd | ||
|
|
2482e91348 | ||
|
|
fcc248ddf6 | ||
|
|
3318ec8c38 | ||
|
|
a416767950 | ||
|
|
173bd01e70 | ||
|
|
de4adeded5 | ||
|
|
674fb34115 | ||
|
|
9c74f0bae5 | ||
|
|
2999adc98f | ||
|
|
be7d8c3347 | ||
|
|
41dd007380 | ||
|
|
83eb73ee03 | ||
|
|
5b2f985710 | ||
|
|
e9c03c4d85 | ||
|
|
d07e277220 | ||
|
|
9bcdb2ede6 | ||
|
|
4bbc4eef1a | ||
|
|
36ad8987dd | ||
|
|
45f1c2ec11 | ||
|
|
705279b6fd | ||
|
|
9ac2dece11 | ||
|
|
325ca434d4 | ||
|
|
128d75a144 | ||
|
|
45375fbd53 | ||
|
|
2d646c0655 | ||
|
|
6f12d06a32 | ||
|
|
bc02aa5eef | ||
|
|
c7ed2daf4a | ||
|
|
5e97dfa5fc | ||
|
|
44666a4944 | ||
|
|
14f6a23f51 | ||
|
|
050b57c72b | ||
|
|
0f891ccb26 | ||
|
|
b3755325ba | ||
|
|
3014948f26 | ||
|
|
1e1fc38c96 | ||
|
|
dad678a4c1 | ||
|
|
f91d8685e3 | ||
|
|
50a2f3b6f9 | ||
|
|
97c7543557 | ||
|
|
e4c8dcf3ca | ||
|
|
5a6158a2e5 | ||
|
|
9936b3ab46 | ||
|
|
673d65db40 | ||
|
|
5e1592274c | ||
|
|
a50fd14ef2 | ||
|
|
baedfa62d2 |
45
README.md
45
README.md
@@ -4,17 +4,27 @@
|
||||
<p align="center">Monitor your applications and troubleshoot problems in your deployed applications, an open-source alternative to DataDog, New Relic, etc.</p>
|
||||
</p>
|
||||
|
||||
[](LICENSE)
|
||||
<p align="center">
|
||||
<img alt="License" src="https://img.shields.io/badge/license-MIT-brightgreen"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/frontend?label=Downloads"> </a>
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNoz_io&hashtags=opensource,signoz,observability">
|
||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||
</p>
|
||||
|
||||
##
|
||||
|
||||
SigNoz is an opensource observability platform. SigNoz uses distributed tracing to gain visibility into your systems and powers data using [Kafka](https://kafka.apache.org/) (to handle high ingestion rate and backpressure) and [Apache Druid](https://druid.apache.org/) (Apache Druid is a high performance real-time analytics database), both proven in the industry to handle scale.
|
||||
SigNoz helps developer monitor applications and troubleshoot problems in their deployed applications. SigNoz uses distributed tracing to gain visibility into your software stack.
|
||||
|
||||
👉 You can see metrics like p99 latency, error rates for your services, external API calls and individual end points.
|
||||
|
||||
👉 You can find the root cause of the problem by going to the exact traces which are causing the problem and see detailed flamegraphs of individual request traces.
|
||||
|
||||
<!--  -->
|
||||
|
||||

|
||||
|
||||
### Features:
|
||||
### 👇 Features:
|
||||
|
||||
- Application overview metrics like RPS, 50th/90th/99th Percentile latencies, and Error Rate
|
||||
- Slowest endpoints in your application
|
||||
@@ -22,16 +32,25 @@ SigNoz is an opensource observability platform. SigNoz uses distributed tracing
|
||||
- Filter traces by service name, operation, latency, error, tags/annotations.
|
||||
- Aggregate metrics on filtered traces. Eg, you can get error rate and 99th percentile latency of `customer_type: gold` or `deployment_version: v2` or `external_call: paypal`
|
||||
- Unified UI for metrics and traces. No need to switch from Prometheus to Jaeger to debug issues.
|
||||
- In-built workflows to reduce your efforts in detecting common issues like new deployment failures, 3rd party slow APIs, etc (Coming Soon)
|
||||
- Anomaly Detection Framework (Coming Soon)
|
||||
|
||||
### Motivation:
|
||||
### 🤓 Why SigNoz?
|
||||
|
||||
- SaaS vendors charge an insane amount to provide Application Monitoring. They often surprise you with huge month end bills without any transparency of data sent to them.
|
||||
- Data privacy and compliance demands data to not leave the network boundary
|
||||
- Highly scalable architecture
|
||||
- No more magic happening in agents installed in your infra. You take control of sampling, uptime, configuration.
|
||||
- Build modules over SigNoz to extend business specific capabilities
|
||||
Being developers, we found it annoying to rely on closed source SaaS vendors for every small feature we wanted. Closed source vendors often surprise you with huge month end bills without any transparency.
|
||||
|
||||
We wanted to make a self-hosted & open source version of tools like DataDog, NewRelic for companies that have privacy and security concerns about having customer data going to third party services.
|
||||
|
||||
Being open source also gives you complete control of your configuration, sampling, uptimes. You can also build modules over SigNoz to extend business specific capabilities
|
||||
|
||||
### 👊🏻 Languages supported:
|
||||
|
||||
We support [OpenTelemetry](https://opentelemetry.io) as the library which you can use to instrument your applications. So any framework and language supported by OpenTelemetry is also supported by SigNoz. Some of the main supported languages are:
|
||||
|
||||
- Java
|
||||
- Python
|
||||
- NodeJS
|
||||
- Go
|
||||
|
||||
You can find the complete list of languages here - https://opentelemetry.io/docs/
|
||||
|
||||
# Getting Started
|
||||
|
||||
@@ -41,9 +60,9 @@ We have a tiny-cluster setup and a standard setup to deploy using docker-compose
|
||||
Follow the steps listed at https://signoz.io/docs/deployment/docker/.
|
||||
The troubleshooting instructions at https://signoz.io/docs/deployment/docker/#troubleshooting may be helpful
|
||||
|
||||
## Deploy in Kubernetes using Helm.
|
||||
## Deploy in Kubernetes using Helm
|
||||
|
||||
Below steps will install the SigNoz in platform namespace inside your k8s cluster.
|
||||
Below steps will install the SigNoz in `platform` namespace inside your k8s cluster.
|
||||
|
||||
```console
|
||||
git clone https://github.com/SigNoz/signoz.git && cd signoz
|
||||
|
||||
@@ -158,7 +158,7 @@ services:
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.2.0
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.2.2
|
||||
container_name: query-service
|
||||
|
||||
depends_on:
|
||||
@@ -173,7 +173,7 @@ services:
|
||||
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.2.1
|
||||
image: signoz/frontend:0.2.3
|
||||
container_name: frontend
|
||||
|
||||
depends_on:
|
||||
|
||||
@@ -153,7 +153,7 @@ services:
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.2.0
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.2.2
|
||||
container_name: query-service
|
||||
|
||||
depends_on:
|
||||
@@ -168,7 +168,7 @@ services:
|
||||
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.2.1
|
||||
image: signoz/frontend:0.2.3
|
||||
container_name: frontend
|
||||
|
||||
depends_on:
|
||||
|
||||
@@ -13,9 +13,9 @@ dependencies:
|
||||
version: 0.2.0
|
||||
- name: query-service
|
||||
repository: file://./signoz-charts/query-service
|
||||
version: 0.2.0
|
||||
version: 0.2.2
|
||||
- name: frontend
|
||||
repository: file://./signoz-charts/frontend
|
||||
version: 0.2.1
|
||||
digest: sha256:7ea89a82fabae53ff97cbdaddab0c9edf952a3d212237efc5897b32937d940fd
|
||||
generated: "2021-05-02T23:16:58.998702+05:30"
|
||||
version: 0.2.3
|
||||
digest: sha256:31c8e3a8a4c89d0e6071c6687f074e88b3eed8ce86310314e5b6f94e5d5017be
|
||||
generated: "2021-05-18T16:54:30.24831+05:30"
|
||||
|
||||
@@ -15,12 +15,12 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.2.0
|
||||
version: 0.2.2
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 0.2.0
|
||||
appVersion: 0.2.2
|
||||
|
||||
dependencies:
|
||||
- name: zookeeper
|
||||
@@ -37,7 +37,7 @@ dependencies:
|
||||
version: 0.2.0
|
||||
- name: query-service
|
||||
repository: "file://./signoz-charts/query-service"
|
||||
version: 0.2.0
|
||||
version: 0.2.2
|
||||
- name: frontend
|
||||
repository: "file://./signoz-charts/frontend"
|
||||
version: 0.2.1
|
||||
version: 0.2.3
|
||||
@@ -14,8 +14,8 @@ type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.2.1
|
||||
version: 0.2.3
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: 0.2.1
|
||||
appVersion: 0.2.3
|
||||
|
||||
@@ -14,8 +14,8 @@ type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.2.0
|
||||
version: 0.2.2
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: 0.2.0
|
||||
appVersion: 0.2.2
|
||||
|
||||
21590
frontend/package-lock.json
generated
Normal file
21590
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@auth0/auth0-react": "^1.2.0",
|
||||
"@babel/core": "7.12.3",
|
||||
"@material-ui/core": "^4.0.0",
|
||||
@@ -88,6 +89,7 @@
|
||||
"react-css-theme-switcher": "^0.1.6",
|
||||
"react-dev-utils": "^11.0.0",
|
||||
"react-dom": "17.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-modal": "^3.12.1",
|
||||
"react-redux": "^7.2.2",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export enum LOCAL_STORAGE {
|
||||
METRICS_TIME_IN_DURATION = "metricsTimeDuration",
|
||||
METRICS_TIME_IN_DURATION = "metricsTimeDurations",
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { Layout, Spin } from "antd";
|
||||
import { Spin } from "antd";
|
||||
import { useThemeSwitcher } from "react-css-theme-switcher";
|
||||
import ROUTES from "Src/constants/routes";
|
||||
import { IS_LOGGED_IN } from "Src/constants/auth";
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Route,
|
||||
Switch,
|
||||
Redirect,
|
||||
} from "react-router-dom";
|
||||
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
|
||||
|
||||
import SideNav from "./Nav/SideNav";
|
||||
import TopNav from "./Nav/TopNav";
|
||||
import BaseLayout from "./BaseLayout";
|
||||
import {
|
||||
ServiceMetrics,
|
||||
ServiceMap,
|
||||
@@ -24,8 +18,6 @@ import {
|
||||
IntstrumentationPage,
|
||||
} from "Src/pages";
|
||||
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
const App = () => {
|
||||
const { status } = useThemeSwitcher();
|
||||
|
||||
@@ -34,47 +26,44 @@ const App = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Router basename="/">
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<SideNav />
|
||||
<Layout className="site-layout">
|
||||
<Content style={{ margin: "0 16px" }}>
|
||||
<TopNav />
|
||||
<Suspense fallback={<Spin size="large" />}>
|
||||
<Switch>
|
||||
<Route path={ROUTES.SIGN_UP} component={Signup} />
|
||||
<Route path={ROUTES.SERVICE_METRICS} component={ServiceMetrics} />
|
||||
<Route path={ROUTES.SERVICE_MAP} component={ServiceMap} />
|
||||
<Route path={ROUTES.TRACES} exact component={TraceDetail} />
|
||||
<Route path={ROUTES.TRACE_GRAPH} component={TraceGraph} />
|
||||
<Route path={ROUTES.SETTINGS} exact component={SettingsPage} />
|
||||
<Route
|
||||
path={ROUTES.INSTRUMENTATION}
|
||||
exact
|
||||
component={IntstrumentationPage}
|
||||
/>
|
||||
<Route path={ROUTES.USAGE_EXPLORER} component={UsageExplorer} />
|
||||
<Route path={ROUTES.APPLICATION} exact component={ServicesTable} />
|
||||
<Route
|
||||
path="/"
|
||||
exact
|
||||
render={() => {
|
||||
return localStorage.getItem(IS_LOGGED_IN) === "yes" ? (
|
||||
<Redirect to={ROUTES.APPLICATION} />
|
||||
) : (
|
||||
<Redirect to={ROUTES.SIGN_UP} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</Content>
|
||||
<Footer style={{ textAlign: "center", fontSize: 10 }}>
|
||||
SigNoz Inc. ©2020{" "}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Router>
|
||||
<BrowserRouter>
|
||||
<Suspense fallback={<Spin size="large" />}>
|
||||
<Route path={"/"}>
|
||||
<Switch>
|
||||
<BaseLayout>
|
||||
<Route path={ROUTES.SIGN_UP} exact component={Signup} />
|
||||
<Route path={ROUTES.APPLICATION} exact component={ServicesTable} />
|
||||
<Route path={ROUTES.SERVICE_METRICS} exact component={ServiceMetrics} />
|
||||
<Route path={ROUTES.SERVICE_MAP} exact component={ServiceMap} />
|
||||
<Route path={ROUTES.TRACES} exact component={TraceDetail} />
|
||||
<Route path={ROUTES.TRACE_GRAPH} exact component={TraceGraph} />
|
||||
<Route path={ROUTES.SETTINGS} exact component={SettingsPage} />
|
||||
<Route
|
||||
path={ROUTES.INSTRUMENTATION}
|
||||
exact
|
||||
component={IntstrumentationPage}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.USAGE_EXPLORER}
|
||||
exactexact
|
||||
component={UsageExplorer}
|
||||
/>
|
||||
<Route
|
||||
path="/"
|
||||
exact
|
||||
render={() => {
|
||||
return localStorage.getItem(IS_LOGGED_IN) === "yes" ? (
|
||||
<Redirect to={ROUTES.APPLICATION} />
|
||||
) : (
|
||||
<Redirect to={ROUTES.SIGN_UP} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</BaseLayout>
|
||||
</Switch>
|
||||
</Route>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
29
frontend/src/modules/BaseLayout.tsx
Normal file
29
frontend/src/modules/BaseLayout.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import { Layout } from "antd";
|
||||
import SideNav from "./Nav/SideNav";
|
||||
import TopNav from "./Nav/TopNav";
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
interface BaseLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const BaseLayout: React.FC<BaseLayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<SideNav />
|
||||
<Layout className="site-layout">
|
||||
<Content style={{ margin: "0 16px" }}>
|
||||
<TopNav />
|
||||
{children}
|
||||
</Content>
|
||||
<Footer style={{ textAlign: "center", fontSize: 10 }}>
|
||||
SigNoz Inc. ©2020{" "}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseLayout;
|
||||
@@ -188,7 +188,7 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps> {
|
||||
ycoordinate={this.state.ycoordinate}
|
||||
>
|
||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
{/* <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements> */}
|
||||
</ChartPopUpUnique>
|
||||
);
|
||||
} else return null;
|
||||
|
||||
@@ -193,7 +193,7 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps> {
|
||||
>
|
||||
View Traces
|
||||
</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
{/* <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements> */}
|
||||
</ChartPopUpUnique>
|
||||
);
|
||||
} else return null;
|
||||
@@ -218,8 +218,8 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps> {
|
||||
borderWidth: 2,
|
||||
},
|
||||
{
|
||||
label: "p90 Latency",
|
||||
data: ndata.map((s) => s.p90 / 1000000), //converting latency from nano sec to ms
|
||||
label: "p95 Latency",
|
||||
data: ndata.map((s) => s.p95 / 1000000), //converting latency from nano sec to ms
|
||||
pointRadius: 0.5,
|
||||
borderColor: "rgba(227, 74, 51, 1.0)",
|
||||
borderWidth: 2,
|
||||
|
||||
@@ -177,7 +177,7 @@ class RequestRateChart extends React.Component<RequestRateChartProps> {
|
||||
ycoordinate={this.state.ycoordinate}
|
||||
>
|
||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
{/* <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements> */}
|
||||
</ChartPopUpUnique>
|
||||
);
|
||||
} else return null;
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Select, Button, Space, Form } from "antd";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Select as DefaultSelect, Button, Space, Form } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { withRouter } from "react-router";
|
||||
import { getLocalStorageRouteKey } from "./utils";
|
||||
import { RouteComponentProps, useLocation } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import ROUTES from "Src/constants/routes";
|
||||
|
||||
import CustomDateTimeModal from "./CustomDateTimeModal";
|
||||
import { GlobalTime, updateTimeInterval } from "../../../store/actions";
|
||||
import { StoreState } from "../../../store/reducers";
|
||||
import FormItem from "antd/lib/form/FormItem";
|
||||
|
||||
import {
|
||||
Options,
|
||||
ServiceMapOptions,
|
||||
DefaultOptionsBasedOnRoute,
|
||||
} from "./config";
|
||||
import { DateTimeRangeType } from "../../../store/actions";
|
||||
import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query";
|
||||
import { LOCAL_STORAGE } from "Src/constants/localStorage";
|
||||
import moment from "moment";
|
||||
const { Option } = Select;
|
||||
const { Option } = DefaultSelect;
|
||||
|
||||
const DateTimeWrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
justify-content: flex-end !important;
|
||||
`;
|
||||
|
||||
const Select = styled(DefaultSelect)``;
|
||||
interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
||||
currentpath?: string;
|
||||
updateTimeInterval: Function;
|
||||
@@ -32,21 +37,34 @@ interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
||||
This components is mounted all the time. Use event listener to track changes.
|
||||
*/
|
||||
const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
const defaultTime = "30min";
|
||||
const location = useLocation();
|
||||
const LocalStorageRouteKey: string = getLocalStorageRouteKey(
|
||||
location.pathname,
|
||||
);
|
||||
const timeDurationInLocalStorage =
|
||||
JSON.parse(localStorage.getItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION)) ||
|
||||
{};
|
||||
const options =
|
||||
location.pathname === ROUTES.SERVICE_MAP ? ServiceMapOptions : Options;
|
||||
let defaultTime = DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
|
||||
? DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
|
||||
: DefaultOptionsBasedOnRoute.default;
|
||||
if (timeDurationInLocalStorage[LocalStorageRouteKey]) {
|
||||
defaultTime = timeDurationInLocalStorage[LocalStorageRouteKey];
|
||||
}
|
||||
const [currentLocalStorageRouteKey, setCurrentLocalStorageRouteKey] = useState(
|
||||
LocalStorageRouteKey,
|
||||
);
|
||||
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
|
||||
const [timeInterval, setTimeInterval] = useState(defaultTime);
|
||||
const [startTime, setStartTime] = useState<moment.Moment | null>(null);
|
||||
const [endTime, setEndTime] = useState<moment.Moment | null>(null);
|
||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
|
||||
const [refreshText, setRefreshText] = useState("");
|
||||
const [refreshButtonClick, setRefreshButtoClick] = useState(0);
|
||||
const [refreshButtonClick, setRefreshButtonClick] = useState(0);
|
||||
const [form_dtselector] = Form.useForm();
|
||||
const location = useLocation();
|
||||
|
||||
const updateTimeOnQueryParamChange = () => {
|
||||
const timeDurationInLocalStorage = localStorage.getItem(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
);
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const intervalInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.interval);
|
||||
const startTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.startTime);
|
||||
@@ -62,36 +80,46 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
const startTime = moment(Number(startTimeString));
|
||||
const endTime = moment(Number(endTimeString));
|
||||
setCustomTime(startTime, endTime, true);
|
||||
} else if (currentLocalStorageRouteKey !== LocalStorageRouteKey) {
|
||||
setMetricsTimeInterval(defaultTime);
|
||||
setCurrentLocalStorageRouteKey(LocalStorageRouteKey);
|
||||
}
|
||||
// first pref: handle intervalInQueryParam
|
||||
else if (intervalInQueryParam) {
|
||||
window.localStorage.setItem(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
intervalInQueryParam,
|
||||
);
|
||||
setMetricsTimeInterval(intervalInQueryParam);
|
||||
} else if (timeDurationInLocalStorage) {
|
||||
setMetricsTimeInterval(timeDurationInLocalStorage);
|
||||
}
|
||||
};
|
||||
|
||||
const setToLocalStorage = (val: string) => {
|
||||
let timeDurationInLocalStorageObj = cloneDeep(timeDurationInLocalStorage);
|
||||
if (timeDurationInLocalStorageObj) {
|
||||
timeDurationInLocalStorageObj[LocalStorageRouteKey] = val;
|
||||
} else {
|
||||
timeDurationInLocalStorageObj = {
|
||||
[LocalStorageRouteKey]: val,
|
||||
};
|
||||
}
|
||||
window.localStorage.setItem(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
JSON.stringify(timeDurationInLocalStorageObj),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMetricsTimeInterval(defaultTime);
|
||||
}, []);
|
||||
|
||||
// On URL Change
|
||||
useEffect(() => {
|
||||
updateTimeOnQueryParamChange();
|
||||
}, [location]);
|
||||
|
||||
//On mount
|
||||
useEffect(() => {
|
||||
updateTimeOnQueryParamChange();
|
||||
}, []);
|
||||
|
||||
const setMetricsTimeInterval = (value: string) => {
|
||||
props.updateTimeInterval(value);
|
||||
setTimeInterval(value);
|
||||
setEndTime(null);
|
||||
setStartTime(null);
|
||||
|
||||
window.localStorage.setItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, value);
|
||||
setToLocalStorage(value);
|
||||
};
|
||||
const setCustomTime = (
|
||||
startTime: moment.Moment,
|
||||
@@ -173,7 +201,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
setRefreshButtoClick(refreshButtonClick + 1);
|
||||
setRefreshButtonClick(refreshButtonClick + 1);
|
||||
setMetricsTimeInterval(timeInterval);
|
||||
};
|
||||
|
||||
@@ -187,15 +215,6 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
};
|
||||
}, [props.location, refreshButtonClick]);
|
||||
|
||||
const options = [
|
||||
{ value: "custom", label: "Custom" },
|
||||
{ value: "15min", label: "Last 15 min" },
|
||||
{ value: "30min", label: "Last 30 min" },
|
||||
{ value: "1hr", label: "Last 1 hour" },
|
||||
{ value: "6hr", label: "Last 6 hour" },
|
||||
{ value: "1day", label: "Last 1 day" },
|
||||
{ value: "1week", label: "Last 1 week" },
|
||||
];
|
||||
if (props.location.pathname.startsWith(ROUTES.USAGE_EXPLORER)) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -205,6 +224,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
"YYYY/MM/DD HH:mm",
|
||||
)}`
|
||||
: timeInterval;
|
||||
|
||||
return (
|
||||
<DateTimeWrapper>
|
||||
<Space style={{ float: "right", display: "block" }}>
|
||||
@@ -256,8 +276,10 @@ const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => {
|
||||
return { globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
export const DateTimeSelector = connect(mapStateToProps, {
|
||||
updateTimeInterval: updateTimeInterval,
|
||||
})(_DateTimeSelector);
|
||||
export const DateTimeSelector = withRouter(
|
||||
connect(mapStateToProps, {
|
||||
updateTimeInterval: updateTimeInterval,
|
||||
})(_DateTimeSelector),
|
||||
);
|
||||
|
||||
export default withRouter(DateTimeSelector);
|
||||
export default DateTimeSelector;
|
||||
|
||||
24
frontend/src/modules/Nav/TopNav/config.ts
Normal file
24
frontend/src/modules/Nav/TopNav/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import ROUTES from "Src/constants/routes";
|
||||
|
||||
export const Options = [
|
||||
{ value: "5min", label: "Last 5 min" },
|
||||
{ value: "15min", label: "Last 15 min" },
|
||||
{ value: "30min", label: "Last 30 min" },
|
||||
{ value: "1hr", label: "Last 1 hour" },
|
||||
{ value: "6hr", label: "Last 6 hour" },
|
||||
{ value: "1day", label: "Last 1 day" },
|
||||
{ value: "1week", label: "Last 1 week" },
|
||||
{ value: "custom", label: "Custom" },
|
||||
];
|
||||
|
||||
export const ServiceMapOptions = [
|
||||
{ value: "1min", label: "Last 1 min" },
|
||||
{ value: "5min", label: "Last 5 min" },
|
||||
];
|
||||
|
||||
export const DefaultOptionsBasedOnRoute = {
|
||||
[ROUTES.SERVICE_MAP]: ServiceMapOptions[0].value,
|
||||
[ROUTES.APPLICATION]: Options[0].value,
|
||||
[ROUTES.SERVICE_METRICS]: Options[2].value,
|
||||
default: Options[2].value,
|
||||
};
|
||||
18
frontend/src/modules/Nav/TopNav/utils.ts
Normal file
18
frontend/src/modules/Nav/TopNav/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import ROUTES from "Src/constants/routes";
|
||||
|
||||
export const getLocalStorageRouteKey = (pathName: string) => {
|
||||
let localStorageKey = "";
|
||||
const pathNameSplit = pathName.split("/");
|
||||
if (!pathNameSplit[2]) {
|
||||
localStorageKey = pathName;
|
||||
} else {
|
||||
Object.keys(ROUTES).forEach((key) => {
|
||||
if (ROUTES[key].indexOf(":") > -1) {
|
||||
if (ROUTES[key].indexOf(pathNameSplit[1]) > -1) {
|
||||
localStorageKey = ROUTES[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return localStorageKey;
|
||||
};
|
||||
75
frontend/src/modules/Servicemap/SelectService.tsx
Normal file
75
frontend/src/modules/Servicemap/SelectService.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useState } from "react";
|
||||
import { servicesItem } from "Src/store/actions";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Select } from "antd";
|
||||
import styled from "styled-components";
|
||||
const { Option } = Select;
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
.info {
|
||||
display:flex;
|
||||
font-family: Roboto;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
color: #4F4F4F;
|
||||
font-size: 14px;
|
||||
.anticon-info-circle {
|
||||
margin-top: 22px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface SelectServiceProps {
|
||||
services: servicesItem[];
|
||||
zoomToService: (arg0: string) => void;
|
||||
zoomToDefault: () => void;
|
||||
}
|
||||
|
||||
const defaultOption = {
|
||||
serviceName: "Default"
|
||||
};
|
||||
|
||||
const SelectService = (props: SelectServiceProps) => {
|
||||
const [selectedVal, setSelectedVal] = useState<string>(defaultOption.serviceName);
|
||||
const { zoomToService, zoomToDefault } = props;
|
||||
const services = cloneDeep(props.services);
|
||||
services.unshift(defaultOption)
|
||||
const handleSelect = (value: string) => {
|
||||
if(value === defaultOption.serviceName){
|
||||
zoomToDefault()
|
||||
} else {
|
||||
zoomToService(value);
|
||||
}
|
||||
setSelectedVal(value);
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<Select
|
||||
style={{ width: 270, marginBottom: "56px" }}
|
||||
placeholder="Select a service"
|
||||
onChange={handleSelect}
|
||||
value={selectedVal}
|
||||
>
|
||||
{services.map(({ serviceName }) => (
|
||||
<Option key={serviceName} value={serviceName}>
|
||||
{serviceName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className='info'>
|
||||
<InfoCircleOutlined />
|
||||
<div>
|
||||
|
||||
<div>-> Size of circles is proportial to the number of requests served by each node </div>
|
||||
<div>-> Click on node name to reposition the node</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectService;
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from "react";
|
||||
// import {useState} from "react";
|
||||
import Graph from "react-graph-vis";
|
||||
// import { graphEvents } from "react-graph-vis";
|
||||
|
||||
//PNOTE - types of react-graph-vis defined in typings folder.
|
||||
//How is it imported directly?
|
||||
// type definition for service graph - https://github.com/crubier/react-graph-vis/issues/80
|
||||
|
||||
// Set shapes - https://visjs.github.io/vis-network/docs/network/nodes.html#
|
||||
// https://github.com/crubier/react-graph-vis/issues/93
|
||||
const graph = {
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
label: "Catalogue",
|
||||
shape: "box",
|
||||
color: "green",
|
||||
border: "black",
|
||||
size: 100,
|
||||
},
|
||||
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
||||
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
||||
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
||||
{ id: 5, label: "Redis-db", shape: "box", color: "#6E6EFD" },
|
||||
],
|
||||
edges: [
|
||||
{ from: 1, to: 2, color: { color: "red" }, size: { size: 20 } },
|
||||
{ from: 2, to: 3, color: { color: "red" } },
|
||||
{ from: 1, to: 3, color: { color: "red" } },
|
||||
{ from: 3, to: 4, color: { color: "red" } },
|
||||
{ from: 3, to: 5, color: { color: "red" } },
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
hierarchical: true,
|
||||
},
|
||||
edges: {
|
||||
color: "#000000",
|
||||
},
|
||||
height: "500px",
|
||||
};
|
||||
|
||||
// const events = {
|
||||
// select: function(event:any) { //PNOTE - TO DO - Get rid of any type
|
||||
// var { nodes, edges } = event;
|
||||
// }
|
||||
// };
|
||||
|
||||
const ServiceGraph = () => {
|
||||
// const [network, setNetwork] = useState(null);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div> Updated Service Graph module coming soon..</div>
|
||||
|
||||
<Graph
|
||||
graph={graph}
|
||||
options={options}
|
||||
// events={events}
|
||||
// getNetwork={network => {
|
||||
// // if you want access to vis.js network api you can set the state in a parent component using this property
|
||||
// }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceGraph;
|
||||
@@ -1,14 +1,147 @@
|
||||
import React from "react";
|
||||
import ServiceGraph from "./ServiceGraph";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import {
|
||||
GlobalTime,
|
||||
serviceMapStore,
|
||||
getServiceMapItems,
|
||||
getDetailedServiceMapItems,
|
||||
} from "Src/store/actions";
|
||||
import { Spin } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { StoreState } from "../../store/reducers";
|
||||
|
||||
const ServiceMap = () => {
|
||||
import { getZoomPx, getGraphData, getTooltip, transformLabel } from "./utils";
|
||||
import SelectService from "./SelectService";
|
||||
import { ForceGraph2D } from "react-force-graph";
|
||||
|
||||
const Container = styled.div`
|
||||
.force-graph-container .graph-tooltip {
|
||||
background: black;
|
||||
padding: 1px;
|
||||
.keyval {
|
||||
display: flex;
|
||||
.key {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.val {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
getServiceMapItems: Function;
|
||||
getDetailedServiceMapItems: Function;
|
||||
}
|
||||
interface graphNode {
|
||||
id: string;
|
||||
group: number;
|
||||
}
|
||||
interface graphLink {
|
||||
source: string;
|
||||
target: string;
|
||||
value: number;
|
||||
}
|
||||
export interface graphDataType {
|
||||
nodes: graphNode[];
|
||||
links: graphLink[];
|
||||
}
|
||||
|
||||
const ServiceMap = (props: ServiceMapProps) => {
|
||||
const fgRef = useRef();
|
||||
const {
|
||||
getDetailedServiceMapItems,
|
||||
getServiceMapItems,
|
||||
globalTime,
|
||||
serviceMap,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
getServiceMapItems(globalTime);
|
||||
getDetailedServiceMapItems(globalTime);
|
||||
}, [globalTime]);
|
||||
|
||||
useEffect(() => {
|
||||
fgRef.current && fgRef.current.d3Force("charge").strength(-400);
|
||||
});
|
||||
if (!serviceMap.items.length || !serviceMap.services.length) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
const zoomToService = (value: string) => {
|
||||
fgRef && fgRef.current.zoomToFit(700, getZoomPx(), (e) => e.id === value);
|
||||
};
|
||||
|
||||
const zoomToDefault = () => {
|
||||
fgRef && fgRef.current.zoomToFit(100, 120);
|
||||
};
|
||||
|
||||
const { nodes, links } = getGraphData(serviceMap);
|
||||
const graphData = { nodes, links };
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
Service Map module coming soon...
|
||||
{/* <ServiceGraph /> */}
|
||||
</div>
|
||||
<Container>
|
||||
<SelectService
|
||||
services={serviceMap.services}
|
||||
zoomToService={zoomToService}
|
||||
zoomToDefault={zoomToDefault}
|
||||
/>
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
cooldownTicks={100}
|
||||
graphData={graphData}
|
||||
nodeLabel={getTooltip}
|
||||
linkAutoColorBy={(d) => d.target}
|
||||
linkDirectionalParticles="value"
|
||||
linkDirectionalParticleSpeed={(d) => d.value}
|
||||
nodeCanvasObject={(node, ctx, globalScale) => {
|
||||
const label = transformLabel(node.id);
|
||||
const fontSize = node.fontSize;
|
||||
ctx.font = `${fontSize}px Roboto`;
|
||||
const width = node.width;
|
||||
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = "#646464";
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
}}
|
||||
onNodeClick={(node) => {
|
||||
const tooltip = document.querySelector(".graph-tooltip");
|
||||
if (tooltip && node) {
|
||||
tooltip.innerHTML = getTooltip(node);
|
||||
}
|
||||
}}
|
||||
nodePointerAreaPaint={(node, color, ctx) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceMap;
|
||||
const mapStateToProps = (
|
||||
state: StoreState,
|
||||
): {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
} => {
|
||||
return {
|
||||
serviceMap: state.serviceMap,
|
||||
globalTime: state.globalTime,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getServiceMapItems: getServiceMapItems,
|
||||
getDetailedServiceMapItems: getDetailedServiceMapItems,
|
||||
})(ServiceMap);
|
||||
|
||||
1
frontend/src/modules/Servicemap/index.ts
Normal file
1
frontend/src/modules/Servicemap/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./ServiceMap";
|
||||
118
frontend/src/modules/Servicemap/utils.ts
Normal file
118
frontend/src/modules/Servicemap/utils.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { uniqBy, uniq, maxBy, cloneDeep, find } from "lodash";
|
||||
import { serviceMapStore } from "Src/store/actions";
|
||||
import { graphDataType } from "./ServiceMap";
|
||||
|
||||
const MIN_WIDTH = 10;
|
||||
const MAX_WIDTH = 20;
|
||||
const DEFAULT_FONT_SIZE = 6;
|
||||
export const getDimensions = (num, highest) => {
|
||||
const percentage = (num / highest) * 100;
|
||||
const width = (percentage * (MAX_WIDTH - MIN_WIDTH)) / 100 + MIN_WIDTH;
|
||||
const fontSize = DEFAULT_FONT_SIZE;
|
||||
return {
|
||||
fontSize,
|
||||
width,
|
||||
};
|
||||
};
|
||||
|
||||
export const getGraphData = (serviceMap: serviceMapStore): graphDataType => {
|
||||
const { items, services } = serviceMap;
|
||||
const highestCallCount = maxBy(items, (e) => e.callCount).callCount;
|
||||
const highestCallRate = maxBy(services, (e) => e.callRate).callRate;
|
||||
const divNum = Number(
|
||||
String(1).padEnd(highestCallCount.toString().length, "0"),
|
||||
);
|
||||
|
||||
const links = cloneDeep(items).map((node) => {
|
||||
const { parent, child, callCount } = node;
|
||||
return {
|
||||
source: parent,
|
||||
target: child,
|
||||
value: (100 - callCount / divNum) * 0.03,
|
||||
};
|
||||
});
|
||||
const uniqParent = uniqBy(cloneDeep(items), "parent").map((e) => e.parent);
|
||||
const uniqChild = uniqBy(cloneDeep(items), "child").map((e) => e.child);
|
||||
const uniqNodes = uniq([...uniqParent, ...uniqChild]);
|
||||
const nodes = uniqNodes.map((node, i) => {
|
||||
const service = find(services, (service) => service.serviceName === node);
|
||||
let color = "#88CEA5";
|
||||
if (!service) {
|
||||
return {
|
||||
id: node,
|
||||
group: i + 1,
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
width: MIN_WIDTH,
|
||||
color,
|
||||
nodeVal: MIN_WIDTH,
|
||||
callRate: 0,
|
||||
errorRate: 0,
|
||||
p99: 0,
|
||||
};
|
||||
}
|
||||
if (service.errorRate > 0) {
|
||||
color = "#F98989";
|
||||
} else if (service.fourXXRate > 0) {
|
||||
color = "#F9DA7B";
|
||||
}
|
||||
const { fontSize, width } = getDimensions(service.callRate, highestCallRate);
|
||||
return {
|
||||
id: node,
|
||||
group: i + 1,
|
||||
fontSize,
|
||||
width,
|
||||
color,
|
||||
nodeVal: width,
|
||||
callRate: service.callRate.toFixed(2),
|
||||
errorRate: service.errorRate,
|
||||
p99: service.p99,
|
||||
};
|
||||
});
|
||||
return {
|
||||
nodes,
|
||||
links,
|
||||
};
|
||||
};
|
||||
|
||||
export const getZoomPx = (): number => {
|
||||
const width = window.screen.width;
|
||||
if (width < 1400) {
|
||||
return 190;
|
||||
} else if (width > 1400 && width < 1700) {
|
||||
return 380;
|
||||
} else if (width > 1700) {
|
||||
return 470;
|
||||
}
|
||||
};
|
||||
|
||||
export const getTooltip = (node: {
|
||||
p99: number;
|
||||
errorRate: number;
|
||||
callRate: number;
|
||||
id: string;
|
||||
}) => {
|
||||
return `<div style="color:#333333;padding:12px;background: white;border-radius: 2px;">
|
||||
<div style="font-weight:bold; margin-bottom:16px;">${node.id}</div>
|
||||
<div class="keyval">
|
||||
<div class="key">P99 latency:</div>
|
||||
<div class="val">${node.p99 / 1000000}ms</div>
|
||||
</div>
|
||||
<div class="keyval">
|
||||
<div class="key">Request:</div>
|
||||
<div class="val">${node.callRate}/sec</div>
|
||||
</div>
|
||||
<div class="keyval">
|
||||
<div class="key">Error Rate:</div>
|
||||
<div class="val">${node.errorRate}%</div>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export const transformLabel = (label: string) => {
|
||||
const MAX_LENGTH = 13;
|
||||
const MAX_SHOW = 10;
|
||||
if (label.length > MAX_LENGTH) {
|
||||
return `${label.slice(0, MAX_SHOW)}...`;
|
||||
}
|
||||
return label;
|
||||
};
|
||||
@@ -23,6 +23,15 @@ export const updateTimeInterval = (
|
||||
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
|
||||
|
||||
switch (interval) {
|
||||
case "1min":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 1 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
case "5min":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 5 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case "15min":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./types";
|
||||
export * from "./traceFilters";
|
||||
export * from "./serviceMap";
|
||||
export * from "./traces";
|
||||
export * from "./metrics";
|
||||
export * from "./usage";
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface servicesListItem {
|
||||
export interface metricItem {
|
||||
timestamp: number;
|
||||
p50: number;
|
||||
p90: number;
|
||||
p95: number;
|
||||
p99: number;
|
||||
numCalls: number;
|
||||
callRate: number;
|
||||
|
||||
78
frontend/src/store/actions/serviceMap.ts
Normal file
78
frontend/src/store/actions/serviceMap.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Dispatch } from "redux";
|
||||
import api, { apiV1 } from "../../api";
|
||||
import { GlobalTime } from "./global";
|
||||
import { ActionTypes } from "./types";
|
||||
|
||||
export interface serviceMapStore {
|
||||
items: servicesMapItem[];
|
||||
services: servicesItem[];
|
||||
}
|
||||
|
||||
export interface servicesItem {
|
||||
serviceName: string;
|
||||
p99: number;
|
||||
avgDuration: number;
|
||||
numCalls: number;
|
||||
callRate: number;
|
||||
numErrors: number;
|
||||
errorRate: number;
|
||||
num4XX: number;
|
||||
fourXXRate: number;
|
||||
}
|
||||
|
||||
export interface servicesMapItem {
|
||||
parent: string;
|
||||
child: string;
|
||||
callCount: number;
|
||||
}
|
||||
|
||||
export interface serviceMapItemAction {
|
||||
type: ActionTypes.getServiceMapItems;
|
||||
payload: servicesMapItem[];
|
||||
}
|
||||
|
||||
export interface servicesAction {
|
||||
type: ActionTypes.getServices;
|
||||
payload: servicesItem[];
|
||||
}
|
||||
|
||||
export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
dispatch<serviceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: [],
|
||||
});
|
||||
|
||||
let request_string =
|
||||
"/serviceMapDependencies?start=" +
|
||||
globalTime.minTime +
|
||||
"&end=" +
|
||||
globalTime.maxTime;
|
||||
|
||||
const response = await api.get<servicesMapItem[]>(apiV1 + request_string);
|
||||
|
||||
dispatch<serviceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: response.data,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
dispatch<servicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: [],
|
||||
});
|
||||
|
||||
let request_string =
|
||||
"/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
|
||||
|
||||
const response = await api.get<servicesItem[]>(apiV1 + request_string);
|
||||
|
||||
dispatch<servicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: response.data,
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getFilteredTraceMetricsAction,
|
||||
getDbOverViewMetricsAction,
|
||||
} from "./metrics";
|
||||
import { serviceMapItemAction, servicesAction } from "./serviceMap";
|
||||
import { getUsageDataAction } from "./usage";
|
||||
import { updateTimeIntervalAction } from "./global";
|
||||
|
||||
@@ -28,6 +29,8 @@ export enum ActionTypes {
|
||||
getUsageData = "GET_USAGE_DATE",
|
||||
updateTimeInterval = "UPDATE_TIME_INTERVAL",
|
||||
getFilteredTraceMetrics = "GET_FILTERED_TRACE_METRICS",
|
||||
getServiceMapItems = "GET_SERVICE_MAP_ITEMS",
|
||||
getServices = "GET_SERVICES",
|
||||
}
|
||||
|
||||
export type Action =
|
||||
@@ -44,4 +47,6 @@ export type Action =
|
||||
| getExternalMetricsAction
|
||||
| externalErrCodeMetricsActions
|
||||
| getDbOverViewMetricsAction
|
||||
| servicesAction
|
||||
| serviceMapItemAction
|
||||
| externalMetricsAvgDurationAction;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
usageDataItem,
|
||||
GlobalTime,
|
||||
externalErrCodeMetricsItem,
|
||||
serviceMapStore,
|
||||
customMetricsItem,
|
||||
TraceFilters,
|
||||
} from "../actions";
|
||||
@@ -27,7 +28,7 @@ import {
|
||||
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
|
||||
import { traceItemReducer, tracesReducer } from "./traces";
|
||||
import { usageDataReducer } from "./usage";
|
||||
|
||||
import { ServiceMapReducer } from "./serviceMap";
|
||||
export interface StoreState {
|
||||
traceFilters: TraceFilters;
|
||||
inputTag: string;
|
||||
@@ -43,6 +44,7 @@ export interface StoreState {
|
||||
usageDate: usageDataItem[];
|
||||
globalTime: GlobalTime;
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
serviceMap: serviceMapStore;
|
||||
}
|
||||
|
||||
const reducers = combineReducers<StoreState>({
|
||||
@@ -60,6 +62,7 @@ const reducers = combineReducers<StoreState>({
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: updateGlobalTimeReducer,
|
||||
filteredTraceMetrics: filteredTraceMetricsReducer,
|
||||
serviceMap: ServiceMapReducer,
|
||||
});
|
||||
|
||||
export default reducers;
|
||||
|
||||
24
frontend/src/store/reducers/serviceMap.ts
Normal file
24
frontend/src/store/reducers/serviceMap.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ActionTypes, Action, serviceMapStore } from "../actions";
|
||||
|
||||
export const ServiceMapReducer = (
|
||||
state: serviceMapStore = {
|
||||
items: [],
|
||||
services: [],
|
||||
},
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getServiceMapItems:
|
||||
return {
|
||||
...state,
|
||||
items: action.payload,
|
||||
};
|
||||
case ActionTypes.getServices:
|
||||
return {
|
||||
...state,
|
||||
services: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -74,6 +74,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/api/v1/tags", aH.searchTags).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/traces/{traceId}", aH.searchTraces).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/usage", aH.getUsage).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/serviceMapDependencies", aH.serviceMapDependencies).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) user(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -280,6 +281,22 @@ func (aH *APIHandler) getServices(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
aH.writeJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) serviceMapDependencies(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseGetServicesRequest(r)
|
||||
if aH.handleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := druidQuery.GetServiceMapDependencies(aH.sqlClient, query)
|
||||
if aH.handleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.writeJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
@@ -241,6 +241,12 @@ func parseSearchSpanAggregatesRequest(r *http.Request) (*model.SpanSearchAggrega
|
||||
zap.S().Debug("Operation Name: ", operationName)
|
||||
}
|
||||
|
||||
kind := r.URL.Query().Get("kind")
|
||||
if len(kind) != 0 {
|
||||
params.Kind = kind
|
||||
zap.S().Debug("Kind: ", kind)
|
||||
}
|
||||
|
||||
minDuration, err := parseTimestamp("minDuration", r)
|
||||
if err == nil {
|
||||
params.MinDuration = *minDuration
|
||||
@@ -292,6 +298,12 @@ func parseSpanSearchRequest(r *http.Request) (*model.SpanSearchParams, error) {
|
||||
zap.S().Debug("Operation Name: ", operationName)
|
||||
}
|
||||
|
||||
kind := r.URL.Query().Get("kind")
|
||||
if len(kind) != 0 {
|
||||
params.Kind = kind
|
||||
zap.S().Debug("Kind: ", kind)
|
||||
}
|
||||
|
||||
minDuration, err := parseTimestamp("minDuration", r)
|
||||
if err == nil {
|
||||
params.MinDuration = *minDuration
|
||||
|
||||
@@ -3,6 +3,7 @@ package druidQuery
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/query-service/constants"
|
||||
@@ -18,10 +19,13 @@ type ServiceItem struct {
|
||||
CallRate float32 `json:"callRate"`
|
||||
NumErrors int `json:"numErrors"`
|
||||
ErrorRate float32 `json:"errorRate"`
|
||||
Num4XX int `json:"num4XX"`
|
||||
FourXXRate float32 `json:"fourXXRate"`
|
||||
}
|
||||
type ServiceListErrorItem struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
NumErrors int `json:"numErrors"`
|
||||
Num4xx int `json:"num4xx"`
|
||||
}
|
||||
|
||||
type ServiceErrorItem struct {
|
||||
@@ -34,7 +38,7 @@ type ServiceOverviewItem struct {
|
||||
Time string `json:"time,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Percentile50 float32 `json:"p50"`
|
||||
Percentile90 float32 `json:"p90"`
|
||||
Percentile95 float32 `json:"p95"`
|
||||
Percentile99 float32 `json:"p99"`
|
||||
NumCalls int `json:"numCalls"`
|
||||
CallRate float32 `json:"callRate"`
|
||||
@@ -62,6 +66,12 @@ type ServiceDBOverviewItem struct {
|
||||
CallRate float32 `json:"callRate,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceMapDependencyItem struct {
|
||||
SpanId string `json:"spanId,omitempty"`
|
||||
ParentSpanId string `json:"parentSpanId,omitempty"`
|
||||
ServiceName string `json:"serviceName,omitempty"`
|
||||
}
|
||||
|
||||
type UsageItem struct {
|
||||
Time string `json:"time,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
@@ -81,6 +91,12 @@ type TagItem struct {
|
||||
TagCount int `json:"tagCount"`
|
||||
}
|
||||
|
||||
type ServiceMapDependencyResponseItem struct {
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Child string `json:"child,omitempty"`
|
||||
CallCount int `json:"callCount,omitempty"`
|
||||
}
|
||||
|
||||
func GetOperations(client *SqlClient, serviceName string) (*[]string, error) {
|
||||
|
||||
sqlQuery := fmt.Sprintf(`SELECT DISTINCT(Name) FROM %s WHERE ServiceName='%s' AND __time > CURRENT_TIMESTAMP - INTERVAL '1' DAY`, constants.DruidDatasource, serviceName)
|
||||
@@ -112,7 +128,7 @@ func GetOperations(client *SqlClient, serviceName string) (*[]string, error) {
|
||||
|
||||
func GetServicesList(client *SqlClient) (*[]string, error) {
|
||||
|
||||
sqlQuery := fmt.Sprintf(`SELECT DISTINCT(ServiceName) FROM %s`, constants.DruidDatasource)
|
||||
sqlQuery := fmt.Sprintf(`SELECT DISTINCT(ServiceName) FROM %s WHERE __time > CURRENT_TIMESTAMP - INTERVAL '1' DAY`, constants.DruidDatasource)
|
||||
// zap.S().Debug(sqlQuery)
|
||||
|
||||
response, err := client.Query(sqlQuery, "array")
|
||||
@@ -319,11 +335,11 @@ func GetServiceExternalErrors(client *SqlClient, query *model.GetServiceOverview
|
||||
return nil, fmt.Errorf("Error in unmarshalling response from druid")
|
||||
}
|
||||
|
||||
m := make(map[int64]int)
|
||||
m := make(map[string]int)
|
||||
|
||||
for j, _ := range *res {
|
||||
timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[j].Time)
|
||||
m[int64(timeObj.UnixNano())] = (*res)[j].NumCalls
|
||||
m[strconv.FormatInt(timeObj.UnixNano(), 10)+"-"+(*res)[j].ExternalHttpUrl] = (*res)[j].NumCalls
|
||||
}
|
||||
|
||||
for i, _ := range *resTotal {
|
||||
@@ -332,7 +348,7 @@ func GetServiceExternalErrors(client *SqlClient, query *model.GetServiceOverview
|
||||
(*resTotal)[i].Time = ""
|
||||
(*resTotal)[i].CallRate = float32((*resTotal)[i].NumCalls) / float32(query.StepSeconds)
|
||||
|
||||
if val, ok := m[(*resTotal)[i].Timestamp]; ok {
|
||||
if val, ok := m[strconv.FormatInt((*resTotal)[i].Timestamp, 10)+"-"+(*resTotal)[i].ExternalHttpUrl]; ok {
|
||||
(*resTotal)[i].NumErrors = val
|
||||
(*resTotal)[i].ErrorRate = float32((*resTotal)[i].NumErrors) * 100 / float32((*resTotal)[i].NumCalls)
|
||||
}
|
||||
@@ -421,7 +437,7 @@ func GetServiceDBOverview(client *SqlClient, query *model.GetServiceOverviewPara
|
||||
|
||||
func GetServiceOverview(client *SqlClient, query *model.GetServiceOverviewParams) (*[]ServiceOverviewItem, error) {
|
||||
|
||||
sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", APPROX_QUANTILE_DS("QuantileDuration", 0.5) as p50, APPROX_QUANTILE_DS("QuantileDuration", 0.9) as p90,
|
||||
sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", APPROX_QUANTILE_DS("QuantileDuration", 0.5) as p50, APPROX_QUANTILE_DS("QuantileDuration", 0.95) as p95,
|
||||
APPROX_QUANTILE_DS("QuantileDuration", 0.99) as p99, COUNT("SpanId") as "numCalls" FROM "%s" WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "ServiceName"='%s' GROUP BY TIME_FLOOR(__time, '%s') `, query.Period, constants.DruidDatasource, query.StartTime, query.EndTime, query.ServiceName, query.Period)
|
||||
|
||||
// zap.S().Debug(sqlQuery)
|
||||
@@ -507,6 +523,8 @@ func GetServices(client *SqlClient, query *model.GetServicesParams) (*[]ServiceI
|
||||
return nil, fmt.Errorf("Error in unmarshalling response from druid")
|
||||
}
|
||||
|
||||
////////////////// Below block gets 5xx of services
|
||||
|
||||
sqlQuery = fmt.Sprintf(`SELECT COUNT(SpanId) as numErrors, "ServiceName" as "serviceName" FROM %s WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "StatusCode">=500 GROUP BY "ServiceName"`, constants.DruidDatasource, query.StartTime, query.EndTime)
|
||||
|
||||
responseError, err := client.Query(sqlQuery, "object")
|
||||
@@ -533,12 +551,48 @@ func GetServices(client *SqlClient, query *model.GetServicesParams) (*[]ServiceI
|
||||
m[(*resError)[j].ServiceName] = (*resError)[j].NumErrors
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
////////////////// Below block gets 4xx of services
|
||||
|
||||
sqlQuery = fmt.Sprintf(`SELECT COUNT(SpanId) as numErrors, "ServiceName" as "serviceName" FROM %s WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "StatusCode">=400 and "StatusCode" < 500 GROUP BY "ServiceName"`, constants.DruidDatasource, query.StartTime, query.EndTime)
|
||||
|
||||
response4xx, err := client.Query(sqlQuery, "object")
|
||||
|
||||
// zap.S().Debug(sqlQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.S().Error(query, err)
|
||||
return nil, fmt.Errorf("Something went wrong in druid query")
|
||||
}
|
||||
|
||||
// zap.S().Info(string(response))
|
||||
|
||||
res4xx := new([]ServiceListErrorItem)
|
||||
err = json.Unmarshal(response4xx, res4xx)
|
||||
if err != nil {
|
||||
zap.S().Error(err)
|
||||
return nil, fmt.Errorf("Error in unmarshalling response from druid")
|
||||
}
|
||||
|
||||
m4xx := make(map[string]int)
|
||||
|
||||
for j, _ := range *res4xx {
|
||||
m4xx[(*res4xx)[j].ServiceName] = (*res4xx)[j].Num4xx
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
for i, _ := range *res {
|
||||
|
||||
if val, ok := m[(*res)[i].ServiceName]; ok {
|
||||
(*res)[i].NumErrors = val
|
||||
}
|
||||
if val, ok := m4xx[(*res)[i].ServiceName]; ok {
|
||||
(*res)[i].Num4XX = val
|
||||
}
|
||||
|
||||
(*res)[i].FourXXRate = float32((*res)[i].Num4XX) * 100 / float32((*res)[i].NumCalls)
|
||||
(*res)[i].ErrorRate = float32((*res)[i].NumErrors) * 100 / float32((*res)[i].NumCalls)
|
||||
(*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.Period)
|
||||
|
||||
@@ -546,3 +600,58 @@ func GetServices(client *SqlClient, query *model.GetServicesParams) (*[]ServiceI
|
||||
servicesResponse := (*res)[1:]
|
||||
return &servicesResponse, nil
|
||||
}
|
||||
|
||||
func GetServiceMapDependencies(client *SqlClient, query *model.GetServicesParams) (*[]ServiceMapDependencyResponseItem, error) {
|
||||
|
||||
sqlQuery := fmt.Sprintf(`SELECT SpanId, ParentSpanId, ServiceName FROM %s WHERE "__time" >= '%s' AND "__time" <= '%s' ORDER BY __time DESC LIMIT 100000`, constants.DruidDatasource, query.StartTime, query.EndTime)
|
||||
|
||||
// zap.S().Debug(sqlQuery)
|
||||
|
||||
response, err := client.Query(sqlQuery, "object")
|
||||
|
||||
if err != nil {
|
||||
zap.S().Error(query, err)
|
||||
return nil, fmt.Errorf("Something went wrong in druid query")
|
||||
}
|
||||
|
||||
// responseStr := string(response)
|
||||
// zap.S().Info(responseStr)
|
||||
|
||||
res := new([]ServiceMapDependencyItem)
|
||||
err = json.Unmarshal(response, res)
|
||||
if err != nil {
|
||||
zap.S().Error(err)
|
||||
return nil, fmt.Errorf("Error in unmarshalling response from druid")
|
||||
}
|
||||
// resCount := len(*res)
|
||||
// fmt.Println(resCount)
|
||||
|
||||
serviceMap := make(map[string]*ServiceMapDependencyResponseItem)
|
||||
|
||||
spanId2ServiceNameMap := make(map[string]string)
|
||||
for i, _ := range *res {
|
||||
spanId2ServiceNameMap[(*res)[i].SpanId] = (*res)[i].ServiceName
|
||||
}
|
||||
for i, _ := range *res {
|
||||
parent2childServiceName := spanId2ServiceNameMap[(*res)[i].ParentSpanId] + "-" + spanId2ServiceNameMap[(*res)[i].SpanId]
|
||||
if _, ok := serviceMap[parent2childServiceName]; !ok {
|
||||
serviceMap[parent2childServiceName] = &ServiceMapDependencyResponseItem{
|
||||
Parent: spanId2ServiceNameMap[(*res)[i].ParentSpanId],
|
||||
Child: spanId2ServiceNameMap[(*res)[i].SpanId],
|
||||
CallCount: 1,
|
||||
}
|
||||
} else {
|
||||
serviceMap[parent2childServiceName].CallCount++
|
||||
}
|
||||
}
|
||||
|
||||
retMe := make([]ServiceMapDependencyResponseItem, 0, len(serviceMap))
|
||||
for _, dependency := range serviceMap {
|
||||
if dependency.Parent == "" {
|
||||
continue
|
||||
}
|
||||
retMe = append(retMe, *dependency)
|
||||
}
|
||||
|
||||
return &retMe, nil
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ func buildFilters(queryParams *model.SpanSearchParams) (*godruid.Filter, error)
|
||||
newFilter := godruid.FilterSelector("Name", queryParams.OperationName)
|
||||
filter = godruid.FilterAnd(filter, newFilter)
|
||||
|
||||
}
|
||||
if len(queryParams.Kind) != 0 {
|
||||
|
||||
newFilter := godruid.FilterSelector("Kind", queryParams.Kind)
|
||||
filter = godruid.FilterAnd(filter, newFilter)
|
||||
|
||||
}
|
||||
|
||||
// zap.S().Debug("MinDuration: ", queryParams.MinDuration)
|
||||
@@ -75,6 +81,8 @@ func buildFilters(queryParams *model.SpanSearchParams) (*godruid.Filter, error)
|
||||
valuesFilter := godruid.FilterSearch("TagsValues", fmt.Sprintf("%s", item.Value))
|
||||
keysFilter := godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key))
|
||||
newFilter = godruid.FilterAnd(valuesFilter, keysFilter)
|
||||
} else if item.Operator == "isnotnull" {
|
||||
newFilter = godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key))
|
||||
} else {
|
||||
return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator)
|
||||
}
|
||||
@@ -110,6 +118,12 @@ func buildFiltersForSpansAggregates(queryParams *model.SpanSearchAggregatesParam
|
||||
newFilter := godruid.FilterSelector("Name", queryParams.OperationName)
|
||||
filter = godruid.FilterAnd(filter, newFilter)
|
||||
|
||||
}
|
||||
if len(queryParams.Kind) != 0 {
|
||||
|
||||
newFilter := godruid.FilterSelector("Kind", queryParams.Kind)
|
||||
filter = godruid.FilterAnd(filter, newFilter)
|
||||
|
||||
}
|
||||
|
||||
// zap.S().Debug("MinDuration: ", queryParams.MinDuration)
|
||||
@@ -140,6 +154,8 @@ func buildFiltersForSpansAggregates(queryParams *model.SpanSearchAggregatesParam
|
||||
valuesFilter := godruid.FilterSearch("TagsValues", fmt.Sprintf("%s", item.Value))
|
||||
keysFilter := godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key))
|
||||
newFilter = godruid.FilterAnd(valuesFilter, keysFilter)
|
||||
} else if item.Operator == "isnotnull" {
|
||||
newFilter = godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key))
|
||||
} else {
|
||||
return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type TagQuery struct {
|
||||
type SpanSearchAggregatesParams struct {
|
||||
ServiceName string
|
||||
OperationName string
|
||||
Kind string
|
||||
MinDuration string
|
||||
MaxDuration string
|
||||
Tags []TagQuery
|
||||
@@ -64,6 +65,7 @@ type SpanSearchAggregatesParams struct {
|
||||
type SpanSearchParams struct {
|
||||
ServiceName string
|
||||
OperationName string
|
||||
Kind string
|
||||
Intervals string
|
||||
MinDuration string
|
||||
MaxDuration string
|
||||
|
||||
Reference in New Issue
Block a user