Compare commits

..

70 Commits
0.2.0 ... 0.2.2

Author SHA1 Message Date
Ankit Anand
55f7f56acf releasing v0.2.2 2021-05-18 16:55:49 +05:30
Ankit Nayan
e6b3a6c9db Merge pull request #107 from SigNoz/issues-106
Display upto 20 characters in name of service in ServiceMap
2021-05-17 18:03:32 +05:30
DIO
d6884cacdb Merge branch 'main' into issues-106 2021-05-17 17:43:04 +05:30
Ankit Nayan
bb155d2356 Merge pull request #109 from SigNoz/issues-93
Add default view in dropdown service-picker in ServiceMap
2021-05-17 17:23:26 +05:30
dhrubesh
c49ffd83a3 remove logs 2021-05-17 14:43:20 +05:30
dhrubesh
8a5178f0dc adds default view option 2021-05-17 14:42:39 +05:30
dhrubesh
057fba112b updates max length 2021-05-17 14:29:35 +05:30
Ankit Nayan
4c0b81b5c7 Merge pull request #108 from SigNoz/remove-zoom-post-stable
Removes abrupt zoom post becoming stable
2021-05-17 11:58:32 +05:30
dhrubesh
1d2f964a63 updates text color 2021-05-17 09:39:20 +05:30
dhrubesh
171fd714de removes zoom post becoming stable 2021-05-17 09:06:05 +05:30
Ankit Anand
789880fa07 changing constants for zoom-in for different screen sizes 2021-05-16 19:55:36 +05:30
Ankit Nayan
f25edf1e29 Merge pull request #102 from SigNoz/issue-92
Change time range in api call of Service Map to 1 min from latest
2021-05-16 19:47:45 +05:30
dhrubesh
c6e2e297d5 resolves conflicts 2021-05-16 18:44:26 +05:30
dhrubesh
2bc01e50bd Merge branch 'issue-92' of github.com-dhrubesh:SigNoz/signoz into issue-92 2021-05-16 18:36:18 +05:30
dhrubesh
38770809e3 handles route specific default value connected to localstorage 2021-05-16 18:35:50 +05:30
Ankit Nayan
9dd9f1133b Merge pull request #104 from SigNoz/issue-103-
Fixes multiple re-renders
2021-05-16 17:23:55 +05:30
Ankit Nayan
8b743f7803 Merge branch 'issue-92' into issue-103- 2021-05-16 17:23:44 +05:30
Ankit Nayan
868b7691b3 Merge pull request #105 from SigNoz/issue-95
Calculate zoom px based on screen size
2021-05-16 17:21:38 +05:30
Ankit Nayan
613e6ba5f9 Merge pull request #106 from SigNoz/issue-97
Adds tooltip on hover
2021-05-16 17:21:25 +05:30
dhrubesh
8fe2fe5aec adds a utility function to transform label 2021-05-16 15:50:32 +05:30
dhrubesh
55a7b5b1b3 adds tooltip on hover 2021-05-16 15:08:31 +05:30
dhrubesh
8b0abbec79 adds default options config by route 2021-05-15 23:24:53 +05:30
dhrubesh
24416ceabd adds width to Select 2021-05-15 19:50:16 +05:30
dhrubesh
2482e91348 calculate zoom px based on screen size 2021-05-15 18:23:29 +05:30
dhrubesh
fcc248ddf6 resets data to avoid multiple re-rendering for parallel apis 2021-05-15 15:18:30 +05:30
dhrubesh
3318ec8c38 removes 1day and adds 5mins 2021-05-15 14:59:47 +05:30
dhrubesh
a416767950 choose config based on routes 2021-05-13 20:07:48 +05:30
dhrubesh
173bd01e70 adds last 1min to store 2021-05-13 20:07:25 +05:30
dhrubesh
de4adeded5 creates 2 diff config for datepicker 2021-05-13 20:06:44 +05:30
Ankit Anand
674fb34115 updated readme 2021-05-11 21:41:42 +05:30
Shweta Bhave
9c74f0bae5 updated readme 2021-05-11 21:34:32 +05:30
Ankit Nayan
2999adc98f added nodes without any dependencies in serviceMap 2021-05-11 13:15:10 +05:30
Ankit Nayan
be7d8c3347 fixed default 4xxErrorRate injected to test 2021-05-10 17:02:58 +05:30
Ankit Nayan
41dd007380 increased speed of particles in serviceMap 2021-05-10 12:07:38 +05:30
Ankit Nayan
83eb73ee03 changing deployment options to 0.2.1 2021-05-10 10:46:16 +05:30
Ankit Nayan
5b2f985710 Merge pull request #91 from SigNoz/disable-options
Disables invalid CTA, updates options based on API payload
2021-05-10 00:10:04 +05:30
dhrubesh
e9c03c4d85 p90->p95 2021-05-10 00:05:49 +05:30
Ankit Nayan
d07e277220 Merge pull request #90 from SigNoz/service-map
Service map view
2021-05-10 00:02:07 +05:30
dhrubesh
9bcdb2ede6 removes set alert 2021-05-10 00:01:50 +05:30
dhrubesh
4bbc4eef1a 399 --> 380 2021-05-09 23:57:50 +05:30
dhrubesh
36ad8987dd cosmetic updates 2021-05-09 23:45:42 +05:30
dhrubesh
45f1c2ec11 removes hardcoding 2021-05-09 23:03:51 +05:30
dhrubesh
705279b6fd fixes zoom issue 2021-05-09 23:02:16 +05:30
dhrubesh
9ac2dece11 UX updates 2021-05-09 22:51:08 +05:30
dhrubesh
325ca434d4 adds height variant 2021-05-09 20:47:56 +05:30
dhrubesh
128d75a144 fixes zoom px and disabledNodeDrag 2021-05-09 19:30:16 +05:30
dhrubesh
45375fbd53 fixes edge case 2021-05-09 19:12:54 +05:30
dhrubesh
2d646c0655 adds hardcoded data 2021-05-09 18:59:49 +05:30
dhrubesh
6f12d06a32 adds selection of service and zoom into node feature 2021-05-09 18:27:37 +05:30
dhrubesh
bc02aa5eef calculate nodes size and color via RPS errorRate 2021-05-09 15:41:57 +05:30
dhrubesh
c7ed2daf4a initial set up with react-force-graph 2021-05-09 14:44:14 +05:30
Pranay Prateek
5e97dfa5fc updated twitter handle for SigNoz 2021-05-09 01:26:25 +05:30
Ankit Nayan
44666a4944 changed p90 to p95 in service overview api 2021-05-06 17:59:54 +05:30
Ankit Nayan
14f6a23f51 Merge branch 'main' of https://github.com/signoz/signoz into main 2021-05-06 17:36:16 +05:30
Ankit Nayan
050b57c72b added ability to query tags with isnotnull operator 2021-05-06 17:35:29 +05:30
Ankit Nayan
0f891ccb26 added kind as field in model for span search 2021-05-06 17:34:55 +05:30
Ankit Nayan
b3755325ba added kind as url param in parser for span search 2021-05-06 17:34:30 +05:30
Pranay Prateek
3014948f26 Merge pull request #85 from pranay01/main
updated badges on README
2021-05-06 17:18:28 +05:30
Pranay Prateek
1e1fc38c96 updated badges 2021-05-06 17:04:15 +05:30
Pranay Prateek
dad678a4c1 updated badges 2021-05-06 16:45:15 +05:30
Pranay Prateek
f91d8685e3 Merge pull request #84 from pranay01/main
updated docker pull badge
2021-05-06 13:18:13 +05:30
Pranay Prateek
50a2f3b6f9 updated docker pull badge 2021-05-06 13:16:41 +05:30
Pranay Prateek
97c7543557 Merge pull request #83 from pranay01/main
updated features section
2021-05-06 12:55:19 +05:30
Pranay Prateek
e4c8dcf3ca updated features section 2021-05-06 12:54:32 +05:30
Pranay Prateek
5a6158a2e5 Merge pull request #82 from pranay01/main
Updated ReadMe
2021-05-06 12:43:35 +05:30
Pranay Prateek
9936b3ab46 updated motivation section 2021-05-06 12:41:52 +05:30
Pranay Prateek
673d65db40 updated intro 2021-05-06 12:39:55 +05:30
Pranay Prateek
5e1592274c updated motivation and intro 2021-05-06 12:31:14 +05:30
Ankit Nayan
a50fd14ef2 fixed bug in External APIs error % mapping 2021-05-05 13:04:09 +05:30
Ankit Nayan
baedfa62d2 added service map api and 4xx rate in /services api 2021-05-05 00:03:57 +05:30
35 changed files with 22438 additions and 213 deletions

View File

@@ -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>
[![MIT](https://img.shields.io/badge/license-MIT-brightgreen)](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.
<!-- ![SigNoz Feature](https://signoz.io/img/readme_feature1.jpg) -->
![SigNoz Feature](https://res.cloudinary.com/dcv3epinx/image/upload/v1618904032/signoz-images/screenzy-1618904013729_clssvy.png)
### 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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -1,3 +1,3 @@
export enum LOCAL_STORAGE {
METRICS_TIME_IN_DURATION = "metricsTimeDuration",
METRICS_TIME_IN_DURATION = "metricsTimeDurations",
}

View File

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

View 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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View 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,
};

View 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;
};

View 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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -0,0 +1 @@
export { default } from "./ServiceMap";

View 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;
};

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
export * from "./types";
export * from "./traceFilters";
export * from "./serviceMap";
export * from "./traces";
export * from "./metrics";
export * from "./usage";

View File

@@ -19,7 +19,7 @@ export interface servicesListItem {
export interface metricItem {
timestamp: number;
p50: number;
p90: number;
p95: number;
p99: number;
numCalls: number;
callRate: number;

View 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,
});
};
};

View File

@@ -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;

View File

@@ -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;

View 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;
}
};

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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