]>
Commit | Line | Data |
---|---|---|
ca70fe93 AM |
1 | #!/usr/bin/python3 |
2 | ||
1b5ebe34 AM |
3 | import os |
4 | import sys | |
5 | import configparser | |
6 | import subprocess | |
7 | import shlex | |
ca70fe93 AM |
8 | from http.server import BaseHTTPRequestHandler, HTTPServer |
9 | from urllib.parse import urlparse | |
10 | from time import sleep, time | |
11 | from pathlib import Path | |
ca70fe93 | 12 | |
1b5ebe34 | 13 | script_dir = os.path.abspath(__file__ + "/../") + "/scripts/" |
ca70fe93 | 14 | proc_path = "/proc/systemtap/__systemtap_exporter" |
ca70fe93 | 15 | |
664aa00b | 16 | |
ca70fe93 | 17 | class Session: |
664aa00b | 18 | |
1b5ebe34 AM |
19 | def __init__(self, name, sess_id): |
20 | self.name = name | |
ca70fe93 | 21 | self.id = sess_id |
1b5ebe34 AM |
22 | self.cmd = self.get_cmd(name) |
23 | self.timeout = None | |
24 | self.process = None | |
25 | self.start_time = None | |
26 | ||
27 | def begin(self): | |
ca70fe93 AM |
28 | self.process = subprocess.Popen(shlex.split(self.cmd)) |
29 | ||
30 | def get_proc_path(self): | |
1b5ebe34 | 31 | return proc_path + str(self.id) + "/" + self.name |
ca70fe93 AM |
32 | |
33 | def get_cmd(self, script): | |
b12df72d AM |
34 | return "stap -m __systemtap_exporter%d %s%s" % (self.id, |
35 | script_dir, | |
36 | script) | |
ca70fe93 | 37 | |
664aa00b | 38 | |
ca70fe93 | 39 | class SessionMgr: |
664aa00b | 40 | |
1b5ebe34 | 41 | def __init__(self): |
664aa00b WC |
42 | self.counter = 0 |
43 | self.sessions = {} | |
44 | self.parse_conf() | |
ca70fe93 | 45 | |
1b5ebe34 | 46 | def create_sess(self, script_name): |
664aa00b WC |
47 | sess = Session(script_name, self.get_new_id()) |
48 | self.sessions[script_name] = sess | |
49 | return sess | |
1b5ebe34 AM |
50 | |
51 | def start_sess(self, sess): | |
52 | """ Begin execution of script and set session's start time """ | |
53 | sess.begin() | |
54 | if self.wait_for_sess_init(sess) != 0: | |
55 | # init failed | |
56 | self.terminate_sess(sess.name, sess) | |
57 | print("Failed to launch " + sess.name) | |
58 | return 1 | |
59 | sess.start_time = time() | |
60 | print("Successfully launched " + sess.name) | |
61 | return 0 | |
62 | ||
63 | def start_sess_from_name(self, script_name): | |
64 | sess = self.sessions[script_name] | |
65 | return self.start_sess(sess) | |
66 | ||
67 | def parse_conf(self): | |
664aa00b WC |
68 | print("Reading config file") |
69 | config = configparser.ConfigParser() | |
1b5ebe34 | 70 | |
664aa00b WC |
71 | try: |
72 | config.read_file(open(script_dir + '/../exporter.conf')) | |
73 | except Exception as e: | |
74 | print("Unable to read exporter.conf: " + str(e)) | |
75 | sys.exit(-1) | |
1b5ebe34 | 76 | |
664aa00b WC |
77 | for sec in config.sections(): |
78 | sess = self.create_sess(sec) | |
1b5ebe34 | 79 | |
664aa00b WC |
80 | if 'timeout' in config[sec]: |
81 | try: | |
82 | sess.timeout = int(config[sec]['timeout']) | |
83 | except: | |
84 | print("Unable to parse option 'timeout' of section " + sec) | |
85 | sys.exit(-1) | |
1b5ebe34 | 86 | |
664aa00b WC |
87 | if 'startup' in config[sec] and config[sec]['startup'] == 'True': |
88 | self.start_sess(sess) | |
1b5ebe34 AM |
89 | |
90 | def sess_exists(self, name): | |
664aa00b | 91 | return name in self.sessions |
1b5ebe34 AM |
92 | |
93 | def sess_started(self, name): | |
664aa00b | 94 | return self.sessions[name].process is not None |
1b5ebe34 AM |
95 | |
96 | def get_new_id(self): | |
664aa00b WC |
97 | ret = self.counter |
98 | self.counter += 1 | |
99 | return ret | |
1b5ebe34 | 100 | |
ca70fe93 | 101 | def wait_for_sess_init(self, sess): |
664aa00b WC |
102 | """ Return 0 if init ok within 30 seconds, else 1. |
103 | Init is considered ok when the session's procfs probe file exists. | |
104 | """ | |
105 | max_wait = 30 | |
106 | pause_duration = 3 | |
107 | path = Path(sess.get_proc_path()) | |
108 | t0 = time() | |
109 | ||
110 | while time() - t0 < max_wait: | |
111 | if path.exists(): | |
112 | return 0 | |
113 | sleep(pause_duration) | |
114 | return 1 | |
ca70fe93 | 115 | |
1b5ebe34 AM |
116 | def terminate_sess(self, name, sess): |
117 | print("Terminating " + name) | |
118 | sess.process.terminate() | |
119 | sess.process = None | |
120 | sess.start_time = None | |
121 | ||
122 | def check_timeouts(self): | |
123 | for (name, sess) in self.sessions.items(): | |
124 | if (sess.start_time is not None | |
125 | and sess.timeout is not None | |
126 | and time() - sess.start_time >= sess.timeout): | |
127 | self.terminate_sess(name, sess) | |
128 | ||
ca70fe93 AM |
129 | |
130 | class HTTPHandler(BaseHTTPRequestHandler): | |
1b5ebe34 | 131 | sessmgr = SessionMgr() |
ca70fe93 AM |
132 | |
133 | def set_headers(self, code, content_type, transfer_encoding=None): | |
134 | self.send_response(code) | |
135 | self.send_header('Content-type', content_type) | |
136 | ||
137 | if transfer_encoding: | |
138 | self.send_header('Transfer-Encoding', transfer_encoding) | |
139 | ||
140 | self.end_headers() | |
141 | ||
142 | def send_metrics(self, sess): | |
143 | metrics_path = sess.get_proc_path() | |
144 | self.set_headers(200, 'text/plain', 'chunked') | |
145 | try: | |
146 | with open(metrics_path) as metrics: | |
147 | for line in metrics: | |
b12df72d | 148 | self.wfile.write(b'%lx\r\n%b\r\n' |
664aa00b | 149 | % (len(line), bytes(line, 'utf-8'))) |
ca70fe93 AM |
150 | except Exception as e: |
151 | msg = bytes(str(e), 'utf-8') | |
152 | self.wfile.write(b'%lx\r\n%b\r\n' % (len(msg), msg)) | |
153 | ||
154 | self.wfile.write(b'0\r\n\r\n') | |
155 | ||
156 | def send_msg(self, code, msg): | |
b12df72d AM |
157 | self.set_headers(code, 'text/plain') |
158 | self.wfile.write(bytes(msg, 'utf-8')) | |
ca70fe93 AM |
159 | |
160 | def do_GET(self): | |
1b5ebe34 AM |
161 | # remove the preceeding '/' from url |
162 | name = urlparse(self.path).path[1:] | |
163 | mgr = self.sessmgr | |
164 | if not mgr.sess_exists(name): | |
165 | # exporter doesn't recognize the url | |
664aa00b | 166 | self.send_msg(404, "Error 404: file not found") |
1b5ebe34 AM |
167 | elif mgr.sess_started(name): |
168 | # session is already running, send metrics | |
169 | self.send_metrics(mgr.sessions[name]) | |
170 | elif mgr.start_sess_from_name(name) == 0: | |
171 | # session successfully launched | |
b12df72d AM |
172 | self.send_msg(200, ("Script successfully started. " |
173 | "Refresh page to access metrics.")) | |
1b5ebe34 AM |
174 | else: |
175 | # session failed to start | |
176 | self.send_msg(500, "Unable to start stap session") | |
ca70fe93 AM |
177 | |
178 | if __name__ == "__main__": | |
179 | server_address = ('', 9900) | |
1b5ebe34 | 180 | sessmgr = HTTPHandler.sessmgr |
ca70fe93 | 181 | httpd = HTTPServer(server_address, HTTPHandler) |
1b5ebe34 AM |
182 | httpd.timeout = 5 |
183 | print("Exporter initialization complete") | |
184 | ||
185 | while 1: | |
186 | httpd.handle_request() | |
187 | sessmgr.check_timeouts() |