<commit_before>package mpopenldap

import (
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"github.com/mackerelio/golib/logging"
	ldap "gopkg.in/ldap.v2"
)

var (
	logger   = logging.GetLogger("metrics.plugin.openldap")
	cnRegexp = regexp.MustCompile("^cn=([^,]+),")
)

// OpenLDAPPlugin plugin for OpenLDAP
type OpenLDAPPlugin struct {
	Prefix             string
	UseTLS             bool
	InsecureSkipVerify bool
	TargetHost         string
	BindDn             string
	ReplBase           string
	ReplMasterHost     string
	ReplMasterUseTLS   bool
	ReplMasterBind     string
	ReplMasterPass     string
	ReplLocalBind      string
	ReplLocalPass      string
	BindPasswd         string
	Tempfile           string
	l                  *ldap.Conn
}

func transformKeyName(key string) string {
	results := cnRegexp.FindStringSubmatch(key)
	if len(results) < 2 {
		return ""
	}
	return strings.Replace(strings.ToLower(strings.TrimSpace(results[1])), " ", "_", -1)
}

func getStats(sr *ldap.SearchResult, prefix string) map[string]float64 {
	stat := make(map[string]float64)
	for _, entry := range sr.Entries {
		for _, attr := range entry.Attributes {
			key := prefix + transformKeyName(entry.DN) + "_" + attr.Name
			value, err := strconv.ParseFloat(entry.GetAttributeValue(attr.Name), 64)
			if err != nil {
				logger.Warningf("Failed to parse value. %s", err)
			}
			stat[key] = value
		}
	}
	return stat
}

func fetchOpenldapMetrics(l *ldap.Conn, base, prefix string, attrs []string) (map[string]float64, error) {
	searchRequest := ldap.NewSearchRequest(base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", attrs, nil)
	sr, err := l.Search(searchRequest)
	if err != nil {
		logger.Errorf("Failed to ldap search. %s.", err)
		return nil, err
	}
	stat := getStats(sr, prefix)
	return stat, nil
}

func mergeStat(dst, src map[string]float64) {
	for k, v := range src {
		dst[k] = v
	}
}

func latestCSN(sr *ldap.SearchResult) (time.Time, error) {
	var res time.Time
	for _, entry := range sr.Entries {
		csns := make([]time.Time, len(entry.Attributes))
		for i, attr := range entry.Attributes {
			v := entry.GetAttributeValue(attr.Name)
			t, err := time.Parse("20060102150405.999999Z", v[0:strings.Index(v, "#")])
			if err != nil {
				return res, err
			}
			csns[i] = t
		}
		sort.Slice(csns, func(i, j int) bool {
			return csns[i].UnixNano() > csns[j].UnixNano()
		})
		res = csns[0]
		return res, nil
	}
	return res, errors.New("not found CSN")
}

func getLatestCSN(host, base, bind, passwd string, useTLS, insecureSkipVerify bool) (time.Time, error) {
	var l *ldap.Conn
	var err error
	var res time.Time
	if useTLS {
		l, err = ldap.DialTLS("tcp", host, &tls.Config{InsecureSkipVerify: insecureSkipVerify})
	} else {
		l, err = ldap.Dial("tcp", host)
	}
	err = l.Bind(bind, passwd)
	if err != nil {
		logger.Errorf("Failed to Bind %s, err: %s", bind, err)
		return res, err
	}
	searchRequest := ldap.NewSearchRequest(base, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", []string{"ContextCSN"}, nil)
	sr, err := l.Search(searchRequest)
	l.Close()
	if err != nil {
		logger.Errorf("Failed to ldap search. %s.", err)
		return res, err
	}
	return latestCSN(sr)

}

// FetchMetrics interface for mackerelplugin
func (m OpenLDAPPlugin) FetchMetrics() (map[string]interface{}, error) {
	stat := make(map[string]float64)
	if m.ReplBase != "" {
		masterTime, err := getLatestCSN(m.ReplMasterHost, m.ReplBase, m.ReplMasterBind, m.ReplMasterPass, m.ReplMasterUseTLS, m.InsecureSkipVerify)
		if err != nil {
			return nil, err
		}
		localTime, err := getLatestCSN(m.TargetHost, m.ReplBase, m.ReplLocalBind, m.ReplLocalPass, m.UseTLS, m.InsecureSkipVerify)
		if err != nil {
			return nil, err
		}
		stat["replication_delay"] = float64(masterTime.UnixNano() - localTime.UnixNano())
	}

	ldapOpes, err := fetchOpenldapMetrics(m.l, "cn=Operations,cn=Monitor", "", []string{"monitorOpInitiated", "monitorOpCompleted"})
	if err != nil {
		return nil, err
	}
	mergeStat(stat, ldapOpes)
	ldapWaiters, err := fetchOpenldapMetrics(m.l, "cn=Waiters,cn=Monitor", "waiters_", []string{"monitorCounter"})
	if err != nil {<commit_msg>fix latestCSN<commit_after>package mpopenldap

import (
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"github.com/mackerelio/golib/logging"
	ldap "gopkg.in/ldap.v2"
)

var (
	logger   = logging.GetLogger("metrics.plugin.openldap")
	cnRegexp = regexp.MustCompile("^cn=([^,]+),")
)

// OpenLDAPPlugin plugin for OpenLDAP
type OpenLDAPPlugin struct {
	Prefix             string
	UseTLS             bool
	InsecureSkipVerify bool
	TargetHost         string
	BindDn             string
	ReplBase           string
	ReplMasterHost     string
	ReplMasterUseTLS   bool
	ReplMasterBind     string
	ReplMasterPass     string
	ReplLocalBind      string
	ReplLocalPass      string
	BindPasswd         string
	Tempfile           string
	l                  *ldap.Conn
}

func transformKeyName(key string) string {
	results := cnRegexp.FindStringSubmatch(key)
	if len(results) < 2 {
		return ""
	}
	return strings.Replace(strings.ToLower(strings.TrimSpace(results[1])), " ", "_", -1)
}

func getStats(sr *ldap.SearchResult, prefix string) map[string]float64 {
	stat := make(map[string]float64)
	for _, entry := range sr.Entries {
		for _, attr := range entry.Attributes {
			key := prefix + transformKeyName(entry.DN) + "_" + attr.Name
			value, err := strconv.ParseFloat(entry.GetAttributeValue(attr.Name), 64)
			if err != nil {
				logger.Warningf("Failed to parse value. %s", err)
			}
			stat[key] = value
		}
	}
	return stat
}

func fetchOpenldapMetrics(l *ldap.Conn, base, prefix string, attrs []string) (map[string]float64, error) {
	searchRequest := ldap.NewSearchRequest(base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", attrs, nil)
	sr, err := l.Search(searchRequest)
	if err != nil {
		logger.Errorf("Failed to ldap search. %s.", err)
		return nil, err
	}
	stat := getStats(sr, prefix)
	return stat, nil
}

func mergeStat(dst, src map[string]float64) {
	for k, v := range src {
		dst[k] = v
	}
}

func latestCSN(sr *ldap.SearchResult) (time.Time, error) {
	var res time.Time
	if len(sr.Entries) == 0 {
		return res, errors.New("not found CSN")
	}
	entry := sr.Entries[0]
	if len(entry.Attributes) == 0 {
		return res, errors.New("not found CSN")
	}
	attr := entry.Attributes[0]
	vs := entry.GetAttributeValues(attr.Name)
	csns := make([]time.Time, len(vs))
	for i, v := range vs {
		t, err := time.Parse("20060102150405.999999Z", v[0:strings.Index(v, "#")])
		if err != nil {
			return res, err
		}
		csns[i] = t
	}
	sort.Slice(csns, func(i, j int) bool {
		return csns[i].UnixNano() > csns[j].UnixNano()
	})
	res = csns[0]
	return res, nil
}

func getLatestCSN(host, base, bind, passwd string, useTLS, insecureSkipVerify bool) (time.Time, error) {
	var l *ldap.Conn
	var err error
	var res time.Time
	if useTLS {
		l, err = ldap.DialTLS("tcp", host, &tls.Config{InsecureSkipVerify: insecureSkipVerify})
	} else {
		l, err = ldap.Dial("tcp", host)
	}
	err = l.Bind(bind, passwd)
	if err != nil {
		logger.Errorf("Failed to Bind %s, err: %s", bind, err)
		return res, err
	}
	searchRequest := ldap.NewSearchRequest(base, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", []string{"ContextCSN"}, nil)
	sr, err := l.Search(searchRequest)
	l.Close()
	if err != nil {
		logger.Errorf("Failed to ldap search. %s.", err)
		return res, err
	}
	return latestCSN(sr)

}

// FetchMetrics interface for mackerelplugin
func (m OpenLDAPPlugin) FetchMetrics() (map[string]interface{}, error) {
	stat := make(map[string]float64)
	if m.ReplBase != "" {
		masterTime, err := getLatestCSN(m.ReplMasterHost, m.ReplBase, m.ReplMasterBind, m.ReplMasterPass, m.ReplMasterUseTLS, m.InsecureSkipVerify)
		if err != nil {
			return nil, err
		}
		localTime, err := getLatestCSN(m.TargetHost, m.ReplBase, m.ReplLocalBind, m.ReplLocalPass, m.UseTLS, m.InsecureSkipVerify)
		if err != nil {
			return nil, err
		}
		stat["replication_delay"] = float64(masterTime.UnixNano()-localTime.UnixNano()) / float64(time.Second)
	}

	ldapOpes, err := fetchOpenldapMetrics(m.l, "cn=Operations,cn=Monitor", "", []string{"monitorOpInitiated", "monitorOpCompleted"})
	if err != nil {
		return nil, err
	}
	mergeStat(stat, ldapOpes)
	ldapWaiters, err := fetchOpenldapMetrics(m.l, "cn=Waiters,cn=Monitor", "waiters_", []string{"monitorCounter"})
	if err != nil {
{'fraction_non_alphanumeric': 0.1446700507614213, 'fraction_numerical': 0.010844485463774804, 'mean_word_length': 3.9171866137266025, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5437572', 'n_tokens_mistral': 3408, 'n_tokens_neox': 3233, 'n_words': 732}