Skip to content

Commit 55948a0

Browse files
Merge pull request #3 from crane-cloud/ft/app-metrics
feat: functionality to retrieve project application metrics from prometheus
2 parents da1aff2 + 3a8cbde commit 55948a0

5 files changed

Lines changed: 331 additions & 1 deletion

File tree

api_docs.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,138 @@ paths:
124124
description: "Bad request"
125125
500:
126126
description: "Internal Server Error"
127+
128+
"/projects/{project_id}/apps/{app_id}/metrics/memory":
129+
post:
130+
tags:
131+
- apps
132+
consumes:
133+
- application/json
134+
produces:
135+
- application/json
136+
parameters:
137+
- in: header
138+
name: Authorization
139+
required: true
140+
description: "Bearer [token]"
141+
type: string
142+
- in: path
143+
name: project_id
144+
required: true
145+
type: string
146+
- in: path
147+
name: app_id
148+
required: true
149+
type: string
150+
- in: body
151+
name: app metrics
152+
schema:
153+
properties:
154+
start:
155+
type: number
156+
format: float
157+
end:
158+
type: number
159+
format: float
160+
step:
161+
type: string
162+
163+
responses:
164+
200:
165+
description: "Metrics for a single App"
166+
404:
167+
description: "App metrics not found"
168+
400:
169+
description: "Bad request"
170+
500:
171+
description: "Internal Server Error"
172+
173+
"/projects/{project_id}/apps/{app_id}/metrics/cpu":
174+
post:
175+
tags:
176+
- apps
177+
consumes:
178+
- application/json
179+
produces:
180+
- application/json
181+
parameters:
182+
- in: header
183+
name: Authorization
184+
required: true
185+
description: "Bearer [token]"
186+
type: string
187+
- in: path
188+
name: project_id
189+
required: true
190+
type: string
191+
- in: path
192+
name: app_id
193+
required: true
194+
type: string
195+
- in: body
196+
name: app metrics
197+
schema:
198+
properties:
199+
start:
200+
type: number
201+
format: float
202+
end:
203+
type: number
204+
format: float
205+
step:
206+
type: string
207+
208+
responses:
209+
200:
210+
description: "Cpu metrics for a single App"
211+
404:
212+
description: "App metrics not found"
213+
400:
214+
description: "Bad request"
215+
500:
216+
description: "Internal Server Error"
217+
218+
"/projects/{project_id}/apps/{app_id}/metrics/network":
219+
post:
220+
tags:
221+
- apps
222+
consumes:
223+
- application/json
224+
produces:
225+
- application/json
226+
parameters:
227+
- in: header
228+
name: Authorization
229+
required: true
230+
description: "Bearer [token]"
231+
type: string
232+
- in: path
233+
name: project_id
234+
required: true
235+
type: string
236+
- in: path
237+
name: app_id
238+
required: true
239+
type: string
240+
- in: body
241+
name: app metrics
242+
schema:
243+
properties:
244+
start:
245+
type: number
246+
format: float
247+
end:
248+
type: number
249+
format: float
250+
step:
251+
type: string
252+
253+
responses:
254+
200:
255+
description: "Network metrics for a single App"
256+
404:
257+
description: "App metrics not found"
258+
400:
259+
description: "Bad request"
260+
500:
261+
description: "Internal Server Error"

app/controllers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .project import (ProjectCPUView, ProjectMemoryUsageView,
22
ProjectNetworkRequestView)
3+
from .app import (AppCpuUsageView, AppMemoryUsageView, AppNetworkUsageView)
34
from .index import (IndexView)

app/controllers/app.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import json
2+
from prometheus_http_client import Prometheus
3+
from flask_restful import Resource, request
4+
5+
from app.helpers.authenticate import (
6+
jwt_required
7+
)
8+
from app.helpers.utils import get_app_data
9+
10+
class AppMemoryUsageView(Resource):
11+
@jwt_required
12+
def post(self, project_id, app_id):
13+
app = get_app_data(project_id, app_id, request)
14+
15+
if app.status_code != 200:
16+
return dict(status='fail', message=app.message), app.status_code
17+
18+
start = app.start
19+
end = app.end
20+
step = app.step
21+
app_alias = app.app_alias
22+
namespace = app.namespace
23+
prometheus = Prometheus()
24+
25+
try:
26+
prom_memory_data = prometheus.query_rang(
27+
start=start,
28+
end=end,
29+
step=step,
30+
metric='sum(rate(container_memory_usage_bytes{container_name!="POD", image!="",pod=~"' + app_alias + '.*", namespace="' + namespace + '"}[5m]))')
31+
except Exception as error:
32+
return dict(status='fail', message=str(error)), 500
33+
34+
new_data = json.loads(prom_memory_data)
35+
final_data_list = []
36+
37+
try:
38+
for value in new_data["data"]["result"][0]["values"]:
39+
mem_case = {'timestamp': float(
40+
value[0]), 'value': float(value[1])}
41+
final_data_list.append(mem_case)
42+
except:
43+
return dict(status='fail', message='No values found'), 404
44+
45+
return dict(status='success', data=dict(values=final_data_list)), 200
46+
47+
48+
class AppCpuUsageView(Resource):
49+
@jwt_required
50+
def post(self, project_id, app_id):
51+
app = get_app_data(project_id, app_id, request)
52+
53+
if app.status_code != 200:
54+
return dict(status='fail', message=app.message), app.status_code
55+
56+
start = app.start
57+
end = app.end
58+
step = app.step
59+
app_alias = app.app_alias
60+
namespace = app.namespace
61+
prometheus = Prometheus()
62+
63+
try:
64+
prom_cpu_data = prometheus.query_rang(
65+
start=start,
66+
end=end,
67+
step=step,
68+
metric='sum(rate(container_cpu_usage_seconds_total{container!="POD", image!="", namespace="' +
69+
namespace + '", pod=~"' + app_alias + '.*"}[5m]))')
70+
except Exception as error:
71+
return dict(status='fail', message=str(error)), 500
72+
73+
new_data = json.loads(prom_cpu_data)
74+
final_data_list = []
75+
76+
try:
77+
for value in new_data["data"]["result"][0]["values"]:
78+
case = {'timestamp': float(value[0]), 'value': float(value[1])}
79+
final_data_list.append(case)
80+
except:
81+
return dict(status='fail', message='No values found'), 404
82+
83+
return dict(status='success', data=dict(values=final_data_list)), 200
84+
85+
86+
class AppNetworkUsageView(Resource):
87+
@jwt_required
88+
def post(self, project_id, app_id):
89+
app = get_app_data(project_id, app_id, request)
90+
91+
if app.status_code != 200:
92+
return dict(status='fail', message=app.message), app.status_code
93+
94+
start = app.start
95+
end = app.end
96+
step = app.step
97+
app_alias = app.app_alias
98+
namespace = app.namespace
99+
prometheus = Prometheus()
100+
101+
try:
102+
prom_net_data = prometheus.query_rang(
103+
start=start,
104+
end=end,
105+
step=step,
106+
metric='sum(rate(container_network_receive_bytes_total{namespace="' +
107+
namespace + '", pod=~"' + app_alias + '.*"}[5m]))'
108+
)
109+
except Exception as error:
110+
return dict(status='fail', message=str(error)), 500
111+
112+
new_data = json.loads(prom_net_data)
113+
final_data_list = []
114+
115+
try:
116+
for value in new_data["data"]["result"][0]["values"]:
117+
case = {'timestamp': float(value[0]), 'value': float(value[1])}
118+
final_data_list.append(case)
119+
except:
120+
return dict(status='fail', message='No values found'), 404
121+
122+
return dict(status='success', data=dict(values=final_data_list)), 200

app/helpers/utils.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
PRODUCT_BASE_URL = os.getenv('PRODUCT_BASE_URL')
88
PROJECT_ENDPOINT = f"{PRODUCT_BASE_URL}/projects"
9+
APP_ENDPOINT = f"{PRODUCT_BASE_URL}/apps"
910

1011

1112
def get_project_data(project_id, request):
@@ -52,3 +53,66 @@ def get_project_data(project_id, request):
5253
namespace=namespace,
5354
status_code=200
5455
)
56+
57+
58+
def get_app_data(project_id, app_id, request):
59+
app_query_data = request.get_json()
60+
61+
metric_schema = MetricsSchema()
62+
63+
validated_query_data = metric_schema.load(
64+
app_query_data)
65+
66+
# if errors:
67+
# return dict(status='fail', message=errors), 400
68+
69+
current_time = datetime.datetime.now()
70+
yesterday_time = current_time + datetime.timedelta(days=-1)
71+
72+
start = validated_query_data.get('start', yesterday_time.timestamp())
73+
end = validated_query_data.get('end', current_time.timestamp())
74+
step = validated_query_data.get('step', '1h')
75+
76+
# get project details
77+
project_response = requests.get(
78+
f"{PROJECT_ENDPOINT}/{project_id}", headers={
79+
"accept": "application/json",
80+
"Authorization": request.headers.get('Authorization')
81+
})
82+
83+
if not project_response.ok:
84+
return SimpleNamespace(status='failed', message="Failed to fetch project for current user", status_code=400)
85+
86+
project_response = project_response.json()
87+
project_data = project_response['data']['project']
88+
89+
# get app details
90+
app_response = requests.get(
91+
f"{APP_ENDPOINT}/{app_id}", headers={
92+
"accept": "application/json",
93+
"Authorization": request.headers.get('Authorization')
94+
}
95+
)
96+
97+
if not app_response.ok:
98+
return SimpleNamespace(status='failed', message="Failed to fetch app for current user", status_code=400)
99+
100+
app_response = app_response.json()
101+
app_data = app_response['data']['apps']
102+
103+
app_alias = app_data['alias']
104+
namespace = project_data['alias']
105+
106+
prometheus_url = project_data['cluster']['prometheus_url']
107+
if not prometheus_url:
108+
return SimpleNamespace(status='fail', message='No prometheus url provided', status_code=404)
109+
110+
os.environ["PROMETHEUS_URL"] = prometheus_url
111+
return SimpleNamespace(
112+
start=start,
113+
end=end,
114+
step=step,
115+
app_alias=app_alias,
116+
namespace=namespace,
117+
status_code=200
118+
)

app/routes/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from flask_restful import Api
22
from app.controllers import (
33
IndexView, ProjectCPUView, ProjectMemoryUsageView,
4-
ProjectNetworkRequestView, IndexView)
4+
ProjectNetworkRequestView, AppCpuUsageView, AppMemoryUsageView, AppNetworkUsageView)
55

66
api = Api()
77

@@ -14,3 +14,11 @@
1414
'/projects/<string:project_id>/metrics/memory')
1515
api.add_resource(ProjectNetworkRequestView,
1616
'/projects/<string:project_id>/metrics/network')
17+
18+
# Apps routes
19+
api.add_resource(AppMemoryUsageView,
20+
'/projects/<string:project_id>/apps/<string:app_id>/metrics/memory')
21+
api.add_resource(
22+
AppCpuUsageView, '/projects/<string:project_id>/apps/<string:app_id>/metrics/cpu')
23+
api.add_resource(AppNetworkUsageView,
24+
'/projects/<string:project_id>/apps/<string:app_id>/metrics/network')

0 commit comments

Comments
 (0)