|
8 | 8 | import subprocess |
9 | 9 | import sys |
10 | 10 | import threading |
11 | | -from typing import TYPE_CHECKING, Any, Callable |
| 11 | +from typing import TYPE_CHECKING, Any, Callable, List |
12 | 12 |
|
13 | 13 | from fysom import Fysom |
14 | 14 |
|
15 | 15 | from instana.log import logger |
16 | 16 | from instana.util import get_default_gateway |
17 | 17 | from instana.util.process_discovery import Discovery |
| 18 | +from instana.util.runtime import is_windows |
18 | 19 | from instana.version import VERSION |
19 | 20 |
|
20 | 21 | if TYPE_CHECKING: |
@@ -103,48 +104,16 @@ def lookup_agent_host(self, e: Any) -> bool: |
103 | 104 | return False |
104 | 105 |
|
105 | 106 | def announce_sensor(self, e: Any) -> bool: |
| 107 | + pid: int = os.getpid() |
106 | 108 | logger.debug( |
107 | | - f"Attempting to make an announcement to the agent on {self.agent.options.agent_host}:{self.agent.options.agent_port}" |
| 109 | + f"Attempting to announce PID {pid} to the agent on {self.agent.options.agent_host}:{self.agent.options.agent_port}" |
108 | 110 | ) |
109 | | - pid = os.getpid() |
110 | 111 |
|
111 | | - try: |
112 | | - if os.path.isfile("/proc/self/cmdline"): |
113 | | - with open("/proc/self/cmdline") as cmd: |
114 | | - cmdinfo = cmd.read() |
115 | | - cmdline = cmdinfo.split("\x00") |
116 | | - else: |
117 | | - # Python doesn't provide a reliable method to determine what |
118 | | - # the OS process command line may be. Here we are forced to |
119 | | - # rely on ps rather than adding a dependency on something like |
120 | | - # psutil which requires dev packages, gcc etc... |
121 | | - proc = subprocess.Popen( |
122 | | - ["ps", "-p", str(pid), "-o", "command"], stdout=subprocess.PIPE |
123 | | - ) |
124 | | - (out, _) = proc.communicate() |
125 | | - parts = out.split(b"\n") |
126 | | - cmdline = [parts[1].decode("utf-8")] |
127 | | - except Exception: |
128 | | - cmdline = sys.argv |
129 | | - logger.debug("announce_sensor", exc_info=True) |
| 112 | + cmdline = self._get_cmdline(pid) |
130 | 113 |
|
131 | 114 | d = Discovery(pid=self.__get_real_pid(), name=cmdline[0], args=cmdline[1:]) |
132 | 115 |
|
133 | | - # If we're on a system with a procfs |
134 | | - if os.path.exists("/proc/"): |
135 | | - try: |
136 | | - # In CentOS 7, some odd things can happen such as: |
137 | | - # PermissionError: [Errno 13] Permission denied: '/proc/6/fd/8' |
138 | | - # Use a try/except as a safety |
139 | | - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
140 | | - sock.connect( |
141 | | - (self.agent.options.agent_host, self.agent.options.agent_port) |
142 | | - ) |
143 | | - path = f"/proc/{pid}/fd/{sock.fileno()}" |
144 | | - d.fd = sock.fileno() |
145 | | - d.inode = os.readlink(path) |
146 | | - except: # noqa: E722 |
147 | | - logger.debug("Error generating file descriptor: ", exc_info=True) |
| 116 | + self._setup_socket_connection(d, pid) |
148 | 117 |
|
149 | 118 | payload = self.agent.announce(d) |
150 | 119 |
|
@@ -189,28 +158,112 @@ def on_good2go(self, _: Any) -> None: |
189 | 158 | def __get_real_pid(self) -> int: |
190 | 159 | """ |
191 | 160 | Attempts to determine the true process ID by querying the |
192 | | - /proc/<pid>/sched file. This works on systems with a proc filesystem. |
193 | | - Otherwise default to os default. |
| 161 | + /proc/<pid>/sched file on Linux systems or using the OS default PID. |
| 162 | + For Windows, we use the standard OS PID as there's no equivalent concept |
| 163 | + of container PIDs vs host PIDs. |
194 | 164 | """ |
195 | 165 | pid = None |
196 | 166 |
|
| 167 | + # For Linux systems with procfs |
197 | 168 | if os.path.exists("/proc/"): |
198 | 169 | sched_file = f"/proc/{os.getpid()}/sched" |
199 | 170 |
|
200 | 171 | if os.path.isfile(sched_file): |
201 | 172 | try: |
202 | | - file = open(sched_file) |
203 | | - line = file.readline() |
204 | | - g = re.search(r"\((\d+),", line) |
205 | | - if g and len(g.groups()) == 1: |
206 | | - pid = int(g.groups()[0]) |
| 173 | + with open(sched_file) as file: |
| 174 | + line = file.readline() |
| 175 | + g = re.search(r"\((\d+),", line) |
| 176 | + if g and len(g.groups()) == 1: |
| 177 | + pid = int(g.groups()[0]) |
207 | 178 | except Exception: |
208 | 179 | logger.debug("parsing sched file failed", exc_info=True) |
209 | 180 |
|
| 181 | + # For Windows or if Linux method failed |
210 | 182 | if pid is None: |
211 | 183 | pid = os.getpid() |
212 | 184 |
|
213 | 185 | return pid |
214 | 186 |
|
| 187 | + def _get_cmdline_windows(self) -> List[str]: |
| 188 | + """ |
| 189 | + Get command line using Windows API |
| 190 | + """ |
| 191 | + import ctypes |
| 192 | + from ctypes import wintypes |
| 193 | + |
| 194 | + GetCommandLineW = ctypes.windll.kernel32.GetCommandLineW |
| 195 | + GetCommandLineW.argtypes = [] |
| 196 | + GetCommandLineW.restype = wintypes.LPCWSTR |
| 197 | + |
| 198 | + cmd = GetCommandLineW() |
| 199 | + # Simple parsing - this is a basic approach and might need refinement |
| 200 | + # for complex command lines with quotes and spaces |
| 201 | + return cmd.split() |
| 202 | + |
| 203 | + def _get_cmdline_linux_proc(self) -> List[str]: |
| 204 | + """ |
| 205 | + Get command line from Linux /proc filesystem |
| 206 | + """ |
| 207 | + with open("/proc/self/cmdline") as cmd: |
| 208 | + cmdinfo = cmd.read() |
| 209 | + return cmdinfo.split("\x00") |
| 210 | + |
| 211 | + def _get_cmdline_unix_ps(self, pid: int) -> List[str]: |
| 212 | + """ |
| 213 | + Get command line using ps command (for Unix-like systems without /proc) |
| 214 | + """ |
| 215 | + proc = subprocess.Popen( |
| 216 | + ["ps", "-p", str(pid), "-o", "command"], stdout=subprocess.PIPE |
| 217 | + ) |
| 218 | + (out, _) = proc.communicate() |
| 219 | + parts = out.split(b"\n") |
| 220 | + return [parts[1].decode("utf-8")] |
| 221 | + |
| 222 | + def _get_cmdline_unix(self, pid: int) -> List[str]: |
| 223 | + """ |
| 224 | + Get command line using Unix |
| 225 | + """ |
| 226 | + if os.path.isfile("/proc/self/cmdline"): |
| 227 | + return self._get_cmdline_linux_proc() |
| 228 | + else: |
| 229 | + return self._get_cmdline_unix_ps(pid) |
| 230 | + |
| 231 | + def _get_cmdline(self, pid: int) -> List[str]: |
| 232 | + """ |
| 233 | + Get command line in a platform-independent way |
| 234 | + """ |
| 235 | + try: |
| 236 | + if is_windows(): |
| 237 | + return self._get_cmdline_windows() |
| 238 | + else: |
| 239 | + return self._get_cmdline_unix(pid) |
| 240 | + except Exception: |
| 241 | + logger.debug("Error getting command line", exc_info=True) |
| 242 | + return sys.argv |
| 243 | + |
| 244 | + def _setup_socket_connection(self, discovery: Discovery, pid: int) -> None: |
| 245 | + """ |
| 246 | + Set up socket connection and populate discovery object with socket details |
| 247 | + """ |
| 248 | + try: |
| 249 | + # In CentOS 7, some odd things can happen such as: |
| 250 | + # PermissionError: [Errno 13] Permission denied: '/proc/6/fd/8' |
| 251 | + # Use a try/except as a safety |
| 252 | + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 253 | + sock.connect((self.agent.options.agent_host, self.agent.options.agent_port)) |
| 254 | + discovery.fd = sock.fileno() |
| 255 | + |
| 256 | + # If we're on a system with a procfs (Linux) |
| 257 | + if os.path.exists("/proc/"): |
| 258 | + try: |
| 259 | + path = "/proc/%d/fd/%d" % (pid, sock.fileno()) |
| 260 | + discovery.inode = os.readlink(path) |
| 261 | + except Exception: |
| 262 | + logger.debug( |
| 263 | + "Error generating file descriptor inode: ", exc_info=True |
| 264 | + ) |
| 265 | + except Exception: |
| 266 | + logger.debug("Error creating socket connection: ", exc_info=True) |
| 267 | + |
215 | 268 |
|
216 | 269 | # Made with Bob |
0 commit comments