ezmetrics 2.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +21 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +41 -0
  8. data/LICENSE +21 -674
  9. data/README.md +68 -143
  10. data/_config.yml +1 -0
  11. data/ezmetrics.gemspec +21 -0
  12. data/lib/ezmetrics.rb +7 -281
  13. data/lib/ezmetrics/benchmark.rb +2 -2
  14. data/lib/ezmetrics/dashboard/app/assets/config/dashboard_manifest.js +2 -0
  15. data/lib/ezmetrics/dashboard/app/assets/images/dashboard/.keep +0 -0
  16. data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/application.js +15 -0
  17. data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/main.js +1 -0
  18. data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/application.css +16 -0
  19. data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/main.css +6 -0
  20. data/lib/ezmetrics/dashboard/app/controllers/dashboard/application_controller.rb +5 -0
  21. data/lib/ezmetrics/dashboard/app/controllers/dashboard/metrics_controller.rb +41 -0
  22. data/lib/ezmetrics/dashboard/app/views/dashboard/metrics/index.html.erb +3 -0
  23. data/lib/ezmetrics/dashboard/app/views/layouts/dashboard/application.html.erb +12 -0
  24. data/lib/ezmetrics/dashboard/bin/rails +14 -0
  25. data/lib/ezmetrics/dashboard/config/routes.rb +5 -0
  26. data/lib/ezmetrics/dashboard/lib/dashboard.rb +4 -0
  27. data/lib/ezmetrics/dashboard/lib/dashboard/ezmetrics.rb +6 -0
  28. data/lib/ezmetrics/dashboard/lib/dashboard/version.rb +3 -0
  29. data/lib/ezmetrics/dashboard/react-dashboard/.gitignore +23 -0
  30. data/lib/ezmetrics/dashboard/react-dashboard/.rescriptsrc.js +9 -0
  31. data/lib/ezmetrics/dashboard/react-dashboard/README.md +0 -0
  32. data/lib/ezmetrics/dashboard/react-dashboard/package-lock.json +15472 -0
  33. data/lib/ezmetrics/dashboard/react-dashboard/package.json +49 -0
  34. data/lib/ezmetrics/dashboard/react-dashboard/public/index.html +18 -0
  35. data/lib/ezmetrics/dashboard/react-dashboard/public/manifest.json +10 -0
  36. data/lib/ezmetrics/dashboard/react-dashboard/public/robots.txt +2 -0
  37. data/lib/ezmetrics/dashboard/react-dashboard/src/App.tsx +216 -0
  38. data/lib/ezmetrics/dashboard/react-dashboard/src/Constants.tsx +74 -0
  39. data/lib/ezmetrics/dashboard/react-dashboard/src/Graph.tsx +71 -0
  40. data/lib/ezmetrics/dashboard/react-dashboard/src/Metric.tsx +21 -0
  41. data/lib/ezmetrics/dashboard/react-dashboard/src/MetricsBlock.tsx +22 -0
  42. data/lib/ezmetrics/dashboard/react-dashboard/src/Nav.tsx +105 -0
  43. data/lib/ezmetrics/dashboard/react-dashboard/src/RequestsBlock.tsx +78 -0
  44. data/lib/ezmetrics/dashboard/react-dashboard/src/index.css +82 -0
  45. data/lib/ezmetrics/dashboard/react-dashboard/src/index.tsx +9 -0
  46. data/lib/ezmetrics/dashboard/react-dashboard/src/react-app-env.d.ts +1 -0
  47. data/lib/ezmetrics/dashboard/react-dashboard/src/setupTests.ts +5 -0
  48. data/lib/ezmetrics/dashboard/react-dashboard/tsconfig.json +25 -0
  49. data/lib/ezmetrics/dashboard/react-dashboard/yarn.lock +11651 -0
  50. data/lib/ezmetrics/storage.rb +289 -0
  51. data/lib/ezmetrics/version.rb +3 -0
  52. data/lib/generators/ezmetrics/initializer_generator.rb +39 -0
  53. data/scripts/compile_assets +20 -0
  54. metadata +51 -3
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "react-dashboard",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@material-ui/core": "^4.9.0",
7
+ "@material-ui/lab": "^4.0.0-alpha.40",
8
+ "@nivo/line": "^0.61.1",
9
+ "@testing-library/jest-dom": "^4.2.4",
10
+ "@testing-library/react": "^9.3.2",
11
+ "@testing-library/user-event": "^7.1.2",
12
+ "@types/jest": "^24.0.0",
13
+ "@types/node": "^12.0.0",
14
+ "@types/react": "^16.9.0",
15
+ "@types/react-dom": "^16.9.0",
16
+ "bootstrap": "^4.4.1",
17
+ "nivo": "^0.31.0",
18
+ "react": "^16.12.0",
19
+ "react-dom": "^16.12.0",
20
+ "react-scripts": "3.3.0",
21
+ "store": "^2.0.12",
22
+ "typescript": "~3.7.2"
23
+ },
24
+ "scripts": {
25
+ "start": "react-scripts start",
26
+ "build": "react-scripts build",
27
+ "build:clean": "rescripts build",
28
+ "test": "react-scripts test",
29
+ "eject": "react-scripts eject"
30
+ },
31
+ "eslintConfig": {
32
+ "extends": "react-app"
33
+ },
34
+ "browserslist": {
35
+ "production": [
36
+ ">0.2%",
37
+ "not dead",
38
+ "not op_mini all"
39
+ ],
40
+ "development": [
41
+ "last 1 chrome version",
42
+ "last 1 firefox version",
43
+ "last 1 safari version"
44
+ ]
45
+ },
46
+ "devDependencies": {
47
+ "@rescripts/cli": "^0.0.13"
48
+ }
49
+ }
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta
8
+ name="description"
9
+ content="Ezmetrics | Dashboard"
10
+ />
11
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12
+ <title>Ezmetrics | Dashboard</title>
13
+ </head>
14
+ <body>
15
+ <noscript>You need to enable JavaScript to run this app.</noscript>
16
+ <div id="ezmetrics-dashboard"></div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "short_name": "Ezmetrics Dashboard",
3
+ "name": "Ezmetrics Dashboard",
4
+ "icons": [
5
+ ],
6
+ "start_url": ".",
7
+ "display": "standalone",
8
+ "theme_color": "#000000",
9
+ "background_color": "#ffffff"
10
+ }
@@ -0,0 +1,2 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
@@ -0,0 +1,216 @@
1
+ import React, { Component } from 'react';
2
+
3
+ import Nav from './Nav';
4
+ import Graph from './Graph';
5
+ import RequestsBlock from './RequestsBlock'
6
+ import MetricsBlock from './MetricsBlock';
7
+ import {allMetricsList, allRefreshIntervals, allTimeframes, defaultOverviewMetrics, defaultGraphMetrics} from './Constants';
8
+
9
+ interface AppProps {}
10
+
11
+ export interface AppState {
12
+ fullscreen: Boolean;
13
+ showNav: Boolean;
14
+ metrics: any;
15
+ frequency: number;
16
+ timeframe: number;
17
+ partition: String;
18
+ metricsUrl: String;
19
+ graphMetricsList: any[];
20
+ overviewMetricsList: any[];
21
+ timeoutFunction: any;
22
+ error: String;
23
+ }
24
+
25
+ export function metricValueToLabel(value: String): String | undefined {
26
+ const foundMetric = allMetricsList.find(metricHash => metricHash.value === value)
27
+ if (foundMetric) {
28
+ return foundMetric.label
29
+ }
30
+ }
31
+
32
+ export function metricLabelToValue(label: String): String | undefined {
33
+ const foundMetric = allMetricsList.find(metricHash => metricHash.label === label)
34
+ if (foundMetric) {
35
+ return foundMetric.value
36
+ }
37
+ }
38
+
39
+
40
+ var store = require('store');
41
+
42
+ export default class App extends Component<AppProps, AppState> {
43
+
44
+ constructor(props: AppProps) {
45
+ super(props);
46
+ this.state = {
47
+ error: "",
48
+ showNav: false,
49
+ metrics: { simple: {}, partitioned: {} },
50
+ timeoutFunction: setTimeout(() => this.fetchByTimeout(), allRefreshIntervals[0].value),
51
+ fullscreen: store.get("fullscreen"),
52
+ metricsUrl: store.get("metricsUrl") || "/dashboard/metrics/aggregate",
53
+ frequency: store.get("frequency") || allRefreshIntervals[0].value,
54
+ timeframe: store.get("timeframe") || allTimeframes[3].value,
55
+ partition: store.get("partition") || "minute",
56
+ overviewMetricsList: store.get("overviewMetricsList") || defaultOverviewMetrics,
57
+ graphMetricsList: store.get("graphMetricsList") || defaultGraphMetrics,
58
+ };
59
+
60
+ this.fetchData = this.fetchData.bind(this)
61
+ this.changeFrequency = this.changeFrequency.bind(this)
62
+ this.changeTimeframe = this.changeTimeframe.bind(this)
63
+ this.changeMetricsUrl = this.changeMetricsUrl.bind(this)
64
+ this.toggleFullscreen = this.toggleFullscreen.bind(this)
65
+ this.toggleNav = this.toggleNav.bind(this)
66
+ this.changeOverviewMetricsList = this.changeOverviewMetricsList.bind(this)
67
+ this.changeGraphMetricsList = this.changeGraphMetricsList.bind(this)
68
+ }
69
+
70
+ componentWillUnmount(){
71
+ this.clearTimeoutFunction()
72
+ }
73
+
74
+ render() {
75
+ let metricsBlockData = Object.assign({}, this.state.metrics.simple);
76
+ delete metricsBlockData.requests;
77
+
78
+ const metricsBlocks = Object.entries(metricsBlockData).map(
79
+ ([name, value]) => {
80
+ return <MetricsBlock key={name} name={name} metrics={value}/>
81
+ }
82
+ )
83
+
84
+ let graphData = null;
85
+
86
+ let partitionedMetricsSize = Object.keys(this.state.metrics.partitioned).length
87
+
88
+ if (partitionedMetricsSize > 0) {
89
+
90
+ let lines = this.intersection(Object.keys(this.state.metrics.partitioned[0]), this.state.graphMetricsList.map(m => m.value))
91
+
92
+ graphData = lines.map((line: any) => {
93
+ return {
94
+ id: metricValueToLabel(line),
95
+ data: this.state.metrics.partitioned.map((hash: any) => {
96
+ return {
97
+ x: this.formatTimestamp(hash.timestamp),
98
+ y: hash[line]
99
+ }
100
+ }
101
+ ).filter((hash: any) => typeof(hash.y) === "number")
102
+ }
103
+ });
104
+ }
105
+
106
+ return (
107
+ <div className="App">
108
+ <div className={this.state.fullscreen ? "container-fluid" : "container"}>
109
+ <div className={this.state.error.length ? "alert alert-danger error-message" : "hidden"}>{this.state.error}</div>
110
+ <nav className="navbar navbar-light bg-light rounded">
111
+ <span className="navbar-brand">Ezmetrics | Dashboard</span>
112
+ <div className="col"><button className="btn btn-outline-secondary fullscreen" onClick={this.toggleFullscreen}>↹</button></div>
113
+ <button onClick={this.toggleNav} className="btn btn-outline-secondary" type="submit">Settings</button>
114
+ <Nav
115
+ changeFrequency={this.changeFrequency}
116
+ changeTimeframe={this.changeTimeframe}
117
+ changeMetricsUrl={this.changeMetricsUrl}
118
+ toggleFullscreen={this.toggleFullscreen}
119
+ changeGraphMetricsList={this.changeGraphMetricsList}
120
+ changeOverviewMetricsList={this.changeOverviewMetricsList}
121
+ state={this.state}
122
+ />
123
+ </nav>
124
+ {this.state.metrics.simple.requests && <RequestsBlock data={this.state.metrics.simple.requests} state={this.state}/>}
125
+ <div className="row">{metricsBlocks}</div>
126
+ <div className="row">
127
+ <div className="col">
128
+ {graphData && graphData.length !== 0 && <Graph data={graphData} state={this.state}/> }
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ );
134
+ }
135
+
136
+ formatTimestamp(timestamp: number): string {
137
+ return new Date(timestamp * 1000).toLocaleTimeString("en-GB")
138
+ }
139
+
140
+ fetchByTimeout() {
141
+ this.clearTimeoutFunction()
142
+ this.fetchData()
143
+ this.setState({timeoutFunction: setTimeout(() => this.fetchByTimeout(), this.state.frequency)});
144
+ }
145
+
146
+ fetchData() {
147
+ const overviewMetrics = this.state.overviewMetricsList.map(m => m.value).join(",")
148
+ const graphMetrics = this.state.graphMetricsList.map(m => m.value).join(",")
149
+
150
+ fetch(`${this.state.metricsUrl}?interval=${this.state.timeframe}&partition=${this.state.partition}&overview_metrics=${overviewMetrics}&graph_metrics=${graphMetrics}`)
151
+ .then(res => res.json())
152
+ .then((data) => this.setState({metrics: data, error: ""}))
153
+ .catch(error => this.setState({error: "Fetching error: " + error.message + "."}))
154
+ }
155
+
156
+ changeFrequency(frequencyValue: number) {
157
+ store.set("frequency", frequencyValue);
158
+ this.setState({frequency: frequencyValue});
159
+ this.fetchByTimeout()
160
+ }
161
+
162
+ changeTimeframe(timeframeValue: number) {
163
+ var partition = "minute";
164
+
165
+ if (timeframeValue < 121) {
166
+ partition = "second";
167
+ } else if (timeframeValue < 3601) {
168
+ partition = "minute";
169
+ } else {
170
+ partition = "hour";
171
+ }
172
+
173
+ store.set("timeframe", timeframeValue);
174
+ store.set("partition", partition);
175
+
176
+ this.setState({timeframe: timeframeValue, partition: partition});
177
+ this.fetchByTimeout()
178
+ }
179
+
180
+ changeOverviewMetricsList(metricsList: String[]) {
181
+ store.set("overviewMetricsList", metricsList)
182
+ this.setState({overviewMetricsList: metricsList});
183
+ }
184
+
185
+ changeGraphMetricsList(metricsList: String[]) {
186
+ store.set("graphMetricsList", metricsList)
187
+ this.setState({graphMetricsList: metricsList});
188
+ }
189
+
190
+ changeMetricsUrl(metricsUrl: String) {
191
+ store.set("metricsUrl", metricsUrl);
192
+ this.setState({metricsUrl: metricsUrl});
193
+ }
194
+
195
+ toggleFullscreen() {
196
+ store.set("fullscreen", !this.state.fullscreen)
197
+
198
+ this.setState(prevState => ({
199
+ fullscreen: !prevState.fullscreen
200
+ }));
201
+ }
202
+
203
+ toggleNav() {
204
+ this.setState(prevState => ({
205
+ showNav: !prevState.showNav
206
+ }));
207
+ }
208
+
209
+ clearTimeoutFunction(){
210
+ window.clearTimeout(this.state.timeoutFunction);
211
+ }
212
+
213
+ intersection(a1: String[], a2: String[]) {
214
+ return a1.filter(x => a2.indexOf(x) > -1)
215
+ }
216
+ }
@@ -0,0 +1,74 @@
1
+ export const allMetricsList = [
2
+ { value: "requests_all", label: "requests: all" },
3
+ { value: "requests_2xx", label: "requests: 2xx" },
4
+ { value: "requests_3xx", label: "requests: 3xx" },
5
+ { value: "requests_4xx", label: "requests: 4xx" },
6
+ { value: "requests_5xx", label: "requests: 5xx" },
7
+
8
+ { value: "duration_avg", label: "duration: avg" },
9
+ { value: "duration_max", label: "duration: max" },
10
+ { value: "duration_percentile_90", label: "duration: 90%" },
11
+ { value: "duration_percentile_95", label: "duration: 95%" },
12
+ { value: "duration_percentile_99", label: "duration: 99%" },
13
+
14
+ { value: "views_avg", label: "views: avg" },
15
+ { value: "views_max", label: "views: max" },
16
+ { value: "views_percentile_90", label: "views: 90%" },
17
+ { value: "views_percentile_95", label: "views: 95%" },
18
+ { value: "views_percentile_99", label: "views: 99%" },
19
+
20
+ { value: "db_avg", label: "db: avg" },
21
+ { value: "db_max", label: "db: max" },
22
+ { value: "db_percentile_90", label: "db: 90%" },
23
+ { value: "db_percentile_95", label: "db: 95%" },
24
+ { value: "db_percentile_99", label: "db: 99%" },
25
+
26
+ { value: "queries_avg", label: "queries: avg" },
27
+ { value: "queries_max", label: "queries: max" },
28
+ { value: "queries_percentile_90", label: "queries: 90%" },
29
+ { value: "queries_percentile_95", label: "queries: 95%" },
30
+ { value: "queries_percentile_99", label: "queries: 99%" }
31
+ ]
32
+
33
+ export const allRefreshIntervals = [
34
+ { label: "1 second", value: 1000 },
35
+ { label: "5 seconds", value: 5000 },
36
+ { label: "10 seconds", value: 10000 },
37
+ { label: "30 seconds", value: 30000 },
38
+ { label: "1 minute", value: 60000 }
39
+ ]
40
+
41
+ export const allTimeframes = [
42
+ { label: "1 minute", value: 60 },
43
+ { label: "2 minutes", value: 120 },
44
+ { label: "30 minutes", value: 1800 },
45
+ { label: "1 hour", value: 3600 },
46
+ { label: "3 hours", value: 10800 },
47
+ { label: "6 hours", value: 21600 },
48
+ { label: "12 hours", value: 43200 },
49
+ { label: "24 hours", value: 86400 }
50
+ ]
51
+
52
+ export const defaultOverviewMetrics = [
53
+ { value: "requests_2xx", label: "requests: 2xx" },
54
+ { value: "requests_3xx", label: "requests: 3xx" },
55
+ { value: "requests_4xx", label: "requests: 4xx" },
56
+ { value: "requests_5xx", label: "requests: 5xx" },
57
+ { value: "duration_avg", label: "duration: avg" },
58
+ { value: "duration_max", label: "duration: max" },
59
+ { value: "views_avg", label: "views: avg" },
60
+ { value: "views_max", label: "views: max" },
61
+ { value: "db_avg", label: "db: avg" },
62
+ { value: "db_max", label: "db: max" },
63
+ { value: "queries_avg", label: "queries: avg" },
64
+ { value: "queries_max", label: "queries: max" }
65
+ ]
66
+
67
+ export const defaultGraphMetrics = [
68
+ { value: "requests_all", label: "requests: all" },
69
+ { value: "requests_5xx", label: "requests: 5xx" },
70
+ { value: "duration_avg", label: "duration: avg" },
71
+ { value: "duration_max", label: "duration: max" },
72
+ { value: "db_avg", label: "db: avg" },
73
+ { value: "db_max", label: "db: max" }
74
+ ]
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+
3
+ import { ResponsiveLine } from '@nivo/line';
4
+
5
+ export default function Graph(props: any): JSX.Element {
6
+ return (
7
+ <div className="nivo">
8
+ <ResponsiveLine
9
+ data={props.data}
10
+ margin={{ top: 20, right: 120, bottom: 64, left: 70 }}
11
+ xScale={{ type: 'point' }}
12
+ yScale={{ type: 'linear', min: 'auto', max: 'auto', stacked: true, reverse: false }}
13
+ axisTop={null}
14
+ axisRight={null}
15
+ axisBottom={{
16
+ orient: 'bottom',
17
+ tickSize: 5,
18
+ tickPadding: 5,
19
+ tickRotation: -60,
20
+ legend: 'time',
21
+ legendOffset: 58,
22
+ legendPosition: 'middle'
23
+ }}
24
+ axisLeft={{
25
+ orient: 'left',
26
+ tickSize: 2,
27
+ tickPadding: 5,
28
+ tickRotation: 0,
29
+ legend: 'value',
30
+ legendOffset: -50,
31
+ legendPosition: 'middle',
32
+ }}
33
+ colors={{ scheme: 'nivo' }}
34
+ pointSize={5}
35
+ pointColor={{ theme: 'background' }}
36
+ pointBorderWidth={1}
37
+ pointBorderColor={{ from: 'serieColor' }}
38
+ pointLabel="y"
39
+ pointLabelYOffset={-12}
40
+ animate={props.state.timeframe === 60}
41
+ useMesh={true}
42
+ legends={[
43
+ {
44
+ anchor: 'bottom-right',
45
+ direction: 'column',
46
+ justify: false,
47
+ translateX: 100,
48
+ translateY: 0,
49
+ itemsSpacing: 0,
50
+ itemDirection: 'left-to-right',
51
+ itemWidth: 80,
52
+ itemHeight: 20,
53
+ itemOpacity: 0.75,
54
+ symbolSize: 10,
55
+ symbolShape: 'circle',
56
+ symbolBorderColor: 'rgba(0, 0, 0, .5)',
57
+ effects: [
58
+ {
59
+ on: 'hover',
60
+ style: {
61
+ itemBackground: 'rgba(0, 0, 0, .03)',
62
+ itemOpacity: 1
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ ]}
68
+ />
69
+ </div>
70
+ );
71
+ }