| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | //go:build cgo && ibm_mq |
| 4 | |
| 5 | package main |
| 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "log/slog" |
| 10 | "os" |
| 11 | "os/user" |
| 12 | "path/filepath" |
| 13 | "strconv" |
| 14 | "strings" |
| 15 | "time" |
| 16 | |
| 17 | "github.com/jessevdk/go-flags" |
| 18 | "github.com/netdata/netdata/go/plugins/cmd/internal/agenthost" |
| 19 | "github.com/netdata/netdata/go/plugins/cmd/internal/discoveryproviders" |
| 20 | "github.com/netdata/netdata/go/plugins/plugin/agent" |
| 21 | "github.com/netdata/netdata/go/plugins/plugin/agent/discovery" |
| 22 | "github.com/netdata/netdata/go/plugins/plugin/agent/policy" |
| 23 | "go.uber.org/automaxprocs/maxprocs" |
| 24 | "golang.org/x/net/http/httpproxy" |
| 25 | |
| 26 | "github.com/netdata/netdata/go/plugins/logger" |
| 27 | "github.com/netdata/netdata/go/plugins/pkg/buildinfo" |
| 28 | "github.com/netdata/netdata/go/plugins/pkg/cli" |
| 29 | "github.com/netdata/netdata/go/plugins/pkg/executable" |
| 30 | "github.com/netdata/netdata/go/plugins/pkg/hostinfo" |
| 31 | "github.com/netdata/netdata/go/plugins/pkg/pluginconfig" |
| 32 | "github.com/netdata/netdata/go/plugins/pkg/terminal" |
| 33 | "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi" |
| 34 | // Register IBM ecosystem collectors |
| 35 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/as400" // Requires CGO |
| 36 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/db2" // Requires CGO |
| 37 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/mq" // MQ monitoring with PCF protocol |
| 38 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/jmx" // Requires CGO |
| 39 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/mp" // Pure Go |
| 40 | _ "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/pmi" // Requires CGO |
| 41 | ) |
| 42 | |
| 43 | const pluginName = "ibm.d.plugin" |
| 44 | |
| 45 | func init() { |
| 46 | // https://github.com/netdata/netdata/issues/8949#issuecomment-638294959 |
| 47 | if v := os.Getenv("TZ"); strings.HasPrefix(v, ":") { |
| 48 | _ = os.Unsetenv("TZ") |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | func main() { |
| 53 | _, _ = maxprocs.Set(maxprocs.Logger(func(s string, args ...interface{}) {})) |
| 54 | |
| 55 | opts, err := parseCLI() |
| 56 | if err != nil { |
| 57 | if cli.IsHelp(err) { |
| 58 | os.Exit(0) |
| 59 | } |
| 60 | os.Exit(1) |
| 61 | } |
| 62 | |
| 63 | if opts.Version { |
| 64 | fmt.Printf("%s.plugin, version: %s\n", executable.Name, buildinfo.Version) |
| 65 | return |
| 66 | } |
| 67 | |
| 68 | if opts.MetricsAuditDataDir != "" && opts.MetricsAuditDuration == "" { |
| 69 | opts.MetricsAuditDuration = "10m" |
| 70 | } |
| 71 | |
| 72 | pluginconfig.MustInit(pluginconfig.InitInput{ |
| 73 | ConfDir: opts.ConfDir, |
| 74 | WatchPath: opts.WatchPath, |
| 75 | }) |
| 76 | |
| 77 | if lvl := pluginconfig.EnvLogLevel(); lvl != "" { |
| 78 | logger.Level.SetByName(lvl) |
| 79 | } |
| 80 | if opts.Debug { |
| 81 | logger.Level.Set(slog.LevelDebug) |
| 82 | } |
| 83 | isTerminal := terminal.IsTerminal() |
| 84 | |
| 85 | // Parse metrics-audit duration if provided. |
| 86 | var auditDuration time.Duration |
| 87 | if opts.MetricsAuditDuration != "" { |
| 88 | var err error |
| 89 | auditDuration, err = time.ParseDuration(opts.MetricsAuditDuration) |
| 90 | if err != nil { |
| 91 | logger.Errorf("error: invalid --metrics-audit duration '%s': %v", opts.MetricsAuditDuration, err) |
| 92 | os.Exit(1) |
| 93 | } |
| 94 | if auditDuration <= 0 { |
| 95 | logger.Errorf("error: invalid --metrics-audit duration '%s': duration must be > 0", opts.MetricsAuditDuration) |
| 96 | os.Exit(1) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | if opts.MetricsAuditDataDir != "" && auditDuration <= 0 { |
| 101 | logger.Errorf("error: --metrics-audit-data requires positive --metrics-audit duration") |
| 102 | os.Exit(1) |
| 103 | } |
| 104 | |
| 105 | metricsAuditDataDir := "" |
| 106 | if opts.MetricsAuditDataDir != "" { |
| 107 | var err error |
| 108 | metricsAuditDataDir, err = prepareMetricsAuditDataDir(opts.MetricsAuditDataDir, pluginconfig.VarLibDir()) |
| 109 | if err != nil { |
| 110 | logger.Errorf("error preparing --metrics-audit-data directory: %v", err) |
| 111 | os.Exit(1) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | a := agent.New(agent.Config{ |
| 116 | Name: executable.Name, |
| 117 | PluginConfigDir: pluginconfig.ConfigDir(), |
| 118 | CollectorsConfigDir: pluginconfig.CollectorsDir(), |
| 119 | VarLibDir: pluginconfig.VarLibDir(), |
| 120 | ModuleRegistry: collectorapi.DefaultRegistry, |
| 121 | IsInsideK8s: hostinfo.IsInsideK8sCluster(), |
| 122 | RunModePolicy: policy.Agent(isTerminal), |
| 123 | DiscoveryProviders: []discovery.ProviderFactory{ |
| 124 | discoveryproviders.File(), |
| 125 | discoveryproviders.Dummy(), |
| 126 | }, |
| 127 | RunModule: opts.Module, |
| 128 | RunJob: opts.Job, |
| 129 | MinUpdateEvery: opts.UpdateEvery, |
| 130 | AuditDuration: auditDuration, |
| 131 | AuditSummary: opts.MetricsAuditSummary, |
| 132 | AuditDataDir: metricsAuditDataDir, |
| 133 | DisableServiceDiscovery: true, |
| 134 | }) |
| 135 | |
| 136 | a.Debugf("plugin: name=%s, %s", a.Name, buildinfo.Info()) |
| 137 | if u, err := user.Current(); err == nil { |
| 138 | a.Debugf("current user: name=%s, uid=%s", u.Username, u.Uid) |
| 139 | } |
| 140 | |
| 141 | proxyCfg := httpproxy.FromEnvironment() |
| 142 | a.Infof("env HTTP_PROXY '%s', HTTPS_PROXY '%s'", proxyCfg.HTTPProxy, proxyCfg.HTTPSProxy) |
| 143 | |
| 144 | a.Infof("directories → config: %s | collectors: %s | sd: %s | varlib: %s", |
| 145 | a.ConfigDir, a.CollectorsConfDir, a.ServiceDiscoveryConfigDir, a.VarLibDir) |
| 146 | |
| 147 | agenthost.Run(a) |
| 148 | } |
| 149 | |
| 150 | type options struct { |
| 151 | cli.Option |
| 152 | MetricsAuditDuration string `long:"metrics-audit" description:"run metrics-audit mode for specified duration (e.g. 30s, 5m) and analyze metric structure"` |
| 153 | MetricsAuditSummary bool `long:"metrics-audit-summary" description:"show consolidated metrics-audit summary across all jobs"` |
| 154 | MetricsAuditDataDir string `long:"metrics-audit-data" description:"write structured metrics-audit artifacts for the selected module to the given directory"` |
| 155 | } |
| 156 | |
| 157 | func parseCLI() (*options, error) { |
| 158 | opt := &options{ |
| 159 | Option: cli.Option{ |
| 160 | UpdateEvery: 1, |
| 161 | }, |
| 162 | } |
| 163 | |
| 164 | parser := flags.NewParser(opt, flags.Default) |
| 165 | parser.Name = executable.Name |
| 166 | parser.Usage = "[OPTIONS] [update every]" |
| 167 | |
| 168 | rest, err := parser.ParseArgs(os.Args) |
| 169 | if err != nil { |
| 170 | return nil, err |
| 171 | } |
| 172 | |
| 173 | if len(rest) > 1 { |
| 174 | if opt.UpdateEvery, err = strconv.Atoi(rest[1]); err != nil { |
| 175 | return nil, err |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return opt, nil |
| 180 | } |
| 181 | |
| 182 | func prepareMetricsAuditDataDir(path string, varLibDir string) (string, error) { |
| 183 | if path == "" { |
| 184 | return "", nil |
| 185 | } |
| 186 | clean := strings.TrimSpace(path) |
| 187 | if !filepath.IsAbs(clean) && varLibDir != "" { |
| 188 | clean = filepath.Join(varLibDir, clean) |
| 189 | } |
| 190 | absolute, err := filepath.Abs(clean) |
| 191 | if err != nil { |
| 192 | return "", err |
| 193 | } |
| 194 | if absolute == "" || absolute == "/" { |
| 195 | return "", fmt.Errorf("refusing to use unsafe metrics-audit-data directory '%s'", absolute) |
| 196 | } |
| 197 | if err := os.MkdirAll(absolute, 0o755); err != nil { |
| 198 | return "", err |
| 199 | } |
| 200 | runDir := filepath.Join(absolute, fmt.Sprintf("run-%s", time.Now().UTC().Format("20060102-150405.000000000"))) |
| 201 | if err := os.MkdirAll(runDir, 0o755); err != nil { |
| 202 | return "", err |
| 203 | } |
| 204 | return runDir, nil |
| 205 | } |