-
Notifications
You must be signed in to change notification settings - Fork 1
/
quilibrium-node-exporter.py
206 lines (166 loc) · 7.92 KB
/
quilibrium-node-exporter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import os
import base64
import base58
from flask import Flask, jsonify, Response
import subprocess
import json
FRAME_FILE = "next_frame_number"
NODE_ENDPOINT = "http://127.0.0.1:8379/quilibrium.node.node.pb.NodeService"
base58_keys = ["peerId"]
bigEndianKeys = ["unconfirmedTokenSupply", "confirmedTokenSupply", "ownedTokens"]
app = Flask(__name__)
def fetch_data(command):
result = subprocess.run(command, capture_output=True, text=True)
return json.loads(result.stdout)
def is_valid_base64(input_string):
try:
decoded_string = base64.b64decode(input_string, validate=True)
return True
except Exception as e:
return False
def get_last_frame():
if os.path.exists(FRAME_FILE):
try:
with open(FRAME_FILE, 'r') as file:
return int(file.read().strip())
except ValueError:
# If there's an error converting the file content to an integer,
# write zero to the file and return zero.
print("Error reading the frame number. Writing zero to the file.")
set_next_frame(0)
return 0
except Exception as e:
# If there's any other kind of error, log it and handle as needed.
print(f"An unexpected error occurred: {e}")
# Handle other exceptions as needed.
return get_peer_max_frame() # Assuming this is a function defined elsewhere.
def set_next_frame(frame_number):
try:
with open(FRAME_FILE, 'w') as file:
file.write(str(frame_number))
except Exception as e:
# If an error occurs, write zero to the file
print(f"Error occurred: {e}. Writing zero to the file.")
try:
with open(FRAME_FILE, 'w') as file:
file.write('0')
except Exception as e:
# Handle the case where writing zero also fails
print(f"Failed to write zero to the file: {e}")
def get_peer_max_frame():
peer_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetPeerInfo'])
max_frames = 0
for peer in peer_info.get('peerInfo'):
peer_mf = int(peer.get('maxFrame', 0))
if peer_mf > max_frames:
max_frames = peer_mf
return max_frames
def fetch_frame_data(from_frame, to_frame):
command = [
'curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetFrames',
'-H', 'Content-Type: application/json',
'-d', f'{{"from_frame_number":{from_frame}, "to_frame_number":{to_frame}, "include_candidates": true}}'
]
#print(command)
result = subprocess.run(command, capture_output=True, text=True)
return json.loads(result.stdout)
def get_latest_frame():
last_frame = get_last_frame()
last_data = []
direction = -1 # start by going backwards
while True:
from_frame = last_frame
to_frame = last_frame + 1
#print(f"Trying frame: {to_frame}")
# fetching data
data = fetch_frame_data(from_frame, to_frame)
#print("Data:")
#print(data)
frame_exists = data.get('truncatedClockFrames') and \
data['truncatedClockFrames'][0].get('frameNumber')
# If frame was not provided even once
if not frame_exists and not last_data:
break
# If frame doesn't exist and we're going backwards
if not frame_exists and direction == -1:
last_frame += direction
# If frame exists and we're going backwards
elif frame_exists and direction == -1:
direction = 1 # change direction to forwards
last_data = data # update last_data
last_frame += direction
# If frame exists and we're going forwards
elif frame_exists and direction == 1:
last_data = data # update last_data
last_frame += direction
# If frame doesn't exist and we're going forwards
elif not frame_exists and direction == 1:
break
# Update the file to contain the next frame pointer
set_next_frame(from_frame)
return last_data
@app.route('/metrics')
def combined_data():
# Use the NODE_ENDPOINT variable in the curl commands
latest_frame = get_latest_frame()
network_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetNetworkInfo'])
peer_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetPeerInfo'])
token_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetTokenInfo'])
# Combine the data
quil_metrics = {
"LatestFrame": latest_frame,
"NetworkInfo": network_info,
"PeerInfo": peer_info,
"TokenInfo": {"tokenInfo": [token_info]}
}
#print(quil_metrics)
# Return the combined data as JSON
#return jsonify(quil_metrics)
# Format the data for Prometheus
prometheus_data = "\n".join(format_to_prometheus(quil_metrics))
# Return the formatted data with the appropriate content type
return Response(prometheus_data, content_type="text/plain")
def format_to_prometheus(data):
"""Format data to Prometheus text format ensuring non-repetitive key names."""
output = []
for main_key, main_value in data.items():
if isinstance(main_value, dict):
for sub_key, sub_value in main_value.items():
if isinstance(sub_value, list):
for index, item in enumerate(sub_value):
labels = {}
# Avoid extending the key name with the same value
metric_name = f"Quilibrium_{main_key}" if main_key.lower() == sub_key.lower() else f"Quilibrium_{main_key}_{sub_key}"
for key, value in item.items():
#print(value)
if key in bigEndianKeys and is_valid_base64(value):
# Base64 decode the value
decoded_bytes = base64.b64decode(value)
# Convert the value to big endian
value = int.from_bytes(decoded_bytes, byteorder='big')
# Combine the base metric name with the current key to create a unique metric name
complete_metric_name = metric_name + f"_{key}"
label_str = ",".join([f'{k}="{v}"' for k, v in labels.items()])
label_part = "{" + label_str + "}" if label_str else ""
metric = f"{complete_metric_name}{label_part} {int(value)}"
output.append(metric)
elif key in base58_keys and is_valid_base64(value):
# Base64 decode the value
decoded_bytes = base64.b64decode(value)
# Base58 encode the decoded bytes
encoded_peerId = base58.b58encode(decoded_bytes).decode('utf-8')
# Update the labels dictionary with the newly encoded value
labels[key] = encoded_peerId
# Check if the value is an integer or an integer represented as a string
elif isinstance(value, int) or (isinstance(value, str) and value.isdigit()):
# Combine the base metric name with the current key to create a unique metric name
complete_metric_name = metric_name + f"_{key}"
label_str = ",".join([f'{k}="{v}"' for k, v in labels.items()])
label_part = "{" + label_str + "}" if label_str else ""
metric = f"{complete_metric_name}{label_part} {int(value)}"
output.append(metric)
else:
labels[key] = value
return output
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8380)