scraper.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package sshcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sshcheckreceiver"
  4. import (
  5. "context"
  6. "errors"
  7. "runtime"
  8. "time"
  9. "go.opentelemetry.io/collector/component"
  10. "go.opentelemetry.io/collector/pdata/pcommon"
  11. "go.opentelemetry.io/collector/pdata/pmetric"
  12. "go.opentelemetry.io/collector/receiver"
  13. "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sshcheckreceiver/internal/configssh"
  14. "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sshcheckreceiver/internal/metadata"
  15. )
  16. var errClientNotInit = errors.New("client not initialized")
  17. type sshcheckScraper struct {
  18. *configssh.Client
  19. *Config
  20. settings component.TelemetrySettings
  21. mb *metadata.MetricsBuilder
  22. }
  23. // start starts the scraper by creating a new SSH Client on the scraper
  24. func (s *sshcheckScraper) start(_ context.Context, host component.Host) error {
  25. var err error
  26. if !supportedOS() {
  27. return errWindowsUnsupported
  28. }
  29. s.Client, err = s.Config.ToClient(host, s.settings)
  30. return err
  31. }
  32. func (s *sshcheckScraper) scrapeSSH(now pcommon.Timestamp) error {
  33. var success int64
  34. start := time.Now()
  35. err := s.Client.Dial(s.Config.SSHClientSettings.Endpoint)
  36. if err == nil {
  37. success = 1
  38. }
  39. s.mb.RecordSshcheckDurationDataPoint(now, time.Since(start).Milliseconds())
  40. s.mb.RecordSshcheckStatusDataPoint(now, success)
  41. return err
  42. }
  43. func (s *sshcheckScraper) scrapeSFTP(now pcommon.Timestamp) error {
  44. var success int64
  45. start := time.Now()
  46. // upgrade to SFTP and read fs
  47. sftpc, err := s.Client.SFTPClient()
  48. if err == nil {
  49. _, err = sftpc.ReadDir(".")
  50. if err == nil {
  51. success = 1
  52. }
  53. }
  54. s.mb.RecordSshcheckSftpDurationDataPoint(now, time.Since(start).Milliseconds())
  55. s.mb.RecordSshcheckSftpStatusDataPoint(now, success)
  56. return err
  57. }
  58. // timeout chooses the shorter between between a given deadline and timeout
  59. func timeout(deadline time.Time, timeout time.Duration) time.Duration {
  60. timeToDeadline := time.Until(deadline)
  61. if timeToDeadline < timeout {
  62. return timeToDeadline
  63. }
  64. return timeout
  65. }
  66. // scrape connects to the endpoint and produces metrics based on the response. TBH the flow-of-control
  67. // is a bit awkward here, because the SFTP checks are not enabled by default and they would panic on nil
  68. // ref to the underlying Conn when SSH checks failed.
  69. func (s *sshcheckScraper) scrape(ctx context.Context) (_ pmetric.Metrics, err error) {
  70. var (
  71. to time.Duration
  72. )
  73. // check cancellation
  74. select {
  75. case <-ctx.Done():
  76. return pmetric.NewMetrics(), ctx.Err()
  77. default:
  78. }
  79. cleanup := func() {
  80. s.Client.Close()
  81. }
  82. // if the context carries a shorter deadline then timeout that quickly
  83. deadline, ok := ctx.Deadline()
  84. if ok {
  85. to = timeout(deadline, s.Client.Timeout)
  86. s.Client.Timeout = to
  87. }
  88. ctx, cancel := context.WithCancel(ctx)
  89. defer cancel()
  90. now := pcommon.NewTimestampFromTime(time.Now())
  91. if s.Client == nil {
  92. return pmetric.NewMetrics(), errClientNotInit
  93. }
  94. if err = s.scrapeSSH(now); err != nil {
  95. s.mb.RecordSshcheckErrorDataPoint(now, int64(1), err.Error())
  96. } else {
  97. go func() {
  98. <-ctx.Done()
  99. cleanup()
  100. }()
  101. }
  102. if s.SFTPEnabled() {
  103. if err := s.scrapeSFTP(now); err != nil {
  104. s.mb.RecordSshcheckSftpErrorDataPoint(now, int64(1), err.Error())
  105. }
  106. }
  107. rb := s.mb.NewResourceBuilder()
  108. rb.SetSSHEndpoint(s.Config.SSHClientSettings.Endpoint)
  109. return s.mb.Emit(metadata.WithResource(rb.Emit())), nil
  110. }
  111. func newScraper(conf *Config, settings receiver.CreateSettings) *sshcheckScraper {
  112. return &sshcheckScraper{
  113. Config: conf,
  114. settings: settings.TelemetrySettings,
  115. mb: metadata.NewMetricsBuilder(conf.MetricsBuilderConfig, settings),
  116. }
  117. }
  118. func supportedOS() bool {
  119. return !(runtime.GOOS == "windows")
  120. }