startPrometheusServer runs the official Prometheus Docker image with a generated config that scrapes the local Coder metrics endpoint. It uses --net=host so the container can reach the host-bound metrics port directly. Only supported on Linux; returns false without error on other platforms. Returns
(ctx context.Context, logger slog.Logger, cfg *devConfig)
| 1140 | // returns false without error on other platforms. |
| 1141 | // Returns true if the server was started or is already running. |
| 1142 | func startPrometheusServer(ctx context.Context, logger slog.Logger, cfg *devConfig) (bool, error) { |
| 1143 | if runtime.GOOS != "linux" { |
| 1144 | logger.Warn(ctx, "prometheus server is only supported on Linux, skipping", |
| 1145 | slog.F("os", runtime.GOOS)) |
| 1146 | return false, nil |
| 1147 | } |
| 1148 | |
| 1149 | // Verify Docker is available before attempting anything. |
| 1150 | if err := exec.CommandContext(ctx, "docker", "info").Run(); err != nil { |
| 1151 | logger.Warn(ctx, "docker not available, skipping prometheus server", |
| 1152 | slog.Error(err)) |
| 1153 | return false, nil |
| 1154 | } |
| 1155 | |
| 1156 | // If the port is already in use, check whether it's our |
| 1157 | // container from a previous run. If so, reuse it. |
| 1158 | if isPortBusy(ctx, prometheusServerPort) { |
| 1159 | out, err := exec.CommandContext(ctx, "docker", "inspect", |
| 1160 | "-f", "{{.State.Running}}", |
| 1161 | prometheusContainerName).Output() |
| 1162 | if err == nil && strings.TrimSpace(string(out)) == "true" { |
| 1163 | logger.Info(ctx, "reusing existing prometheus server", |
| 1164 | slog.F("ui", fmt.Sprintf("http://localhost:%d", prometheusServerPort)), |
| 1165 | slog.F("note", fmt.Sprintf("scrape target may differ from current --prometheus-port %d; restart to apply", cfg.coderMetricsPort))) |
| 1166 | return true, nil |
| 1167 | } |
| 1168 | logger.Info(ctx, "prometheus server port already in use, skipping", |
| 1169 | slog.F("port", prometheusServerPort)) |
| 1170 | return false, nil |
| 1171 | } |
| 1172 | |
| 1173 | // Remove any stopped leftover container from a previous run. |
| 1174 | // Failure is fine; it just means the container doesn't exist. |
| 1175 | rmCmd := exec.CommandContext(ctx, "docker", "rm", "-f", prometheusContainerName) //nolint:gosec |
| 1176 | rmCmd.Stdout = nil |
| 1177 | rmCmd.Stderr = nil |
| 1178 | _ = rmCmd.Run() |
| 1179 | |
| 1180 | // Persist TSDB data across dev environment restarts. The |
| 1181 | // container runs as nobody (UID 65534), so the directory must |
| 1182 | // be world-writable. os.MkdirAll applies the umask, so we |
| 1183 | // chmod explicitly after creation. |
| 1184 | prometheusDataDir := filepath.Join(cfg.configDir, "prometheus") |
| 1185 | if err := os.MkdirAll(prometheusDataDir, 0o777); err != nil { |
| 1186 | return false, xerrors.Errorf("creating prometheus data directory: %w", err) |
| 1187 | } |
| 1188 | if err := os.Chmod(prometheusDataDir, 0o777); err != nil { |
| 1189 | return false, xerrors.Errorf("chmod prometheus data directory: %w", err) |
| 1190 | } |
| 1191 | |
| 1192 | // Write a minimal scrape config to a temp file. |
| 1193 | promCfg := fmt.Sprintf(`global: |
| 1194 | scrape_interval: 15s |
| 1195 | |
| 1196 | scrape_configs: |
| 1197 | - job_name: coder |
| 1198 | scheme: http |
| 1199 | static_configs: |