|
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