// +build linux package sysstat import ( "bufio" "encoding/csv" "fmt" "io" "os" "os/exec" "path" "strconv" "strings" "sync" "time" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" ) var ( firstTimestamp time.Time execCommand = exec.Command // execCommand is used to mock commands in tests. dfltActivities = []string{"DISK"} ) const parseInterval = 1 // parseInterval is the interval (in seconds) where the parsing of the binary file takes place. type Sysstat struct { // Sadc represents the path to the sadc collector utility. Sadc string `toml:"sadc_path"` // Force the execution time of sadc SadcInterval internal.Duration `toml:"sadc_interval"` // Sadf represents the path to the sadf cmd. Sadf string `toml:"sadf_path"` // Activities is a list of activities that are passed as argument to the // collector utility (e.g: DISK, SNMP etc...) // The more activities that are added, the more data is collected. Activities []string // Options is a map of options. // // The key represents the actual option that the Sadf command is called with and // the value represents the description for that option. // // For example, if you have the following options map: // map[string]string{"-C": "cpu", "-d": "disk"} // The Sadf command is run with the options -C and -d to extract cpu and // disk metrics from the collected binary file. // // If Group is false (see below), each metric will be prefixed with the corresponding description // and represents itself a measurement. // // If Group is true, metrics are grouped to a single measurement with the corresponding description as name. Options map[string]string // Group determines if metrics are grouped or not. Group bool // DeviceTags adds the possibility to add additional tags for devices. DeviceTags map[string][]map[string]string `toml:"device_tags"` tmpFile string interval int Log telegraf.Logger } func (*Sysstat) Description() string { return "Sysstat metrics collector" } var sampleConfig = ` ## Path to the sadc command. # ## Common Defaults: ## Debian/Ubuntu: /usr/lib/sysstat/sadc ## Arch: /usr/lib/sa/sadc ## RHEL/CentOS: /usr/lib64/sa/sadc sadc_path = "/usr/lib/sa/sadc" # required ## Path to the sadf command, if it is not in PATH # sadf_path = "/usr/bin/sadf" ## Activities is a list of activities, that are passed as argument to the ## sadc collector utility (e.g: DISK, SNMP etc...) ## The more activities that are added, the more data is collected. # activities = ["DISK"] ## Group metrics to measurements. ## ## If group is false each metric will be prefixed with a description ## and represents itself a measurement. ## ## If Group is true, corresponding metrics are grouped to a single measurement. # group = true ## Options for the sadf command. The values on the left represent the sadf ## options and the values on the right their description (which are used for ## grouping and prefixing metrics). ## ## Run 'sar -h' or 'man sar' to find out the supported options for your ## sysstat version. [inputs.sysstat.options] -C = "cpu" -B = "paging" -b = "io" -d = "disk" # requires DISK activity "-n ALL" = "network" "-P ALL" = "per_cpu" -q = "queue" -R = "mem" -r = "mem_util" -S = "swap_util" -u = "cpu_util" -v = "inode" -W = "swap" -w = "task" # -H = "hugepages" # only available for newer linux distributions # "-I ALL" = "interrupts" # requires INT activity ## Device tags can be used to add additional tags for devices. ## For example the configuration below adds a tag vg with value rootvg for ## all metrics with sda devices. # [[inputs.sysstat.device_tags.sda]] # vg = "rootvg" ` func (*Sysstat) SampleConfig() string { return sampleConfig } func (s *Sysstat) Gather(acc telegraf.Accumulator) error { if s.SadcInterval.Duration != 0 { // Collect interval is calculated as interval - parseInterval s.interval = int(s.SadcInterval.Duration.Seconds()) + parseInterval } if s.interval == 0 { if firstTimestamp.IsZero() { firstTimestamp = time.Now() } else { s.interval = int(time.Since(firstTimestamp).Seconds() + 0.5) } } ts := time.Now().Add(time.Duration(s.interval) * time.Second) if err := s.collect(); err != nil { return err } var wg sync.WaitGroup for option := range s.Options { wg.Add(1) go func(acc telegraf.Accumulator, option string) { defer wg.Done() acc.AddError(s.parse(acc, option, ts)) }(acc, option) } wg.Wait() if _, err := os.Stat(s.tmpFile); err == nil { acc.AddError(os.Remove(s.tmpFile)) } return nil } // collect collects sysstat data with the collector utility sadc. // It runs the following command: // Sadc -S -S ... 2 tmpFile // The above command collects system metrics during and // saves it in binary form to tmpFile. func (s *Sysstat) collect() error { options := []string{} for _, act := range s.Activities { options = append(options, "-S", act) } s.tmpFile = path.Join("/tmp", fmt.Sprintf("sysstat-%d", time.Now().Unix())) // collectInterval has to be smaller than the telegraf data collection interval collectInterval := s.interval - parseInterval // If true, interval is not defined yet and Gather is run for the first time. if collectInterval < 0 { collectInterval = 1 // In that case we only collect for 1 second. } options = append(options, strconv.Itoa(collectInterval), "2", s.tmpFile) cmd := execCommand(s.Sadc, options...) out, err := internal.CombinedOutputTimeout(cmd, time.Second*time.Duration(collectInterval+parseInterval)) if err != nil { if err := os.Remove(s.tmpFile); err != nil { s.Log.Errorf("Failed to remove tmp file after %q command: %s", strings.Join(cmd.Args, " "), err.Error()) } return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out)) } return nil } func filterEnviron(env []string, prefix string) []string { newenv := env[:0] for _, envvar := range env { if !strings.HasPrefix(envvar, prefix) { newenv = append(newenv, envvar) } } return newenv } // Return the Cmd with its environment configured to use the C locale func withCLocale(cmd *exec.Cmd) *exec.Cmd { var env []string if cmd.Env != nil { env = cmd.Env } else { env = os.Environ() } env = filterEnviron(env, "LANG") env = filterEnviron(env, "LC_") env = append(env, "LANG=C") cmd.Env = env return cmd } // parse runs Sadf on the previously saved tmpFile: // Sadf -p -- -p