mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2025-07-23 05:11:40 +02:00
move maybe-usable-by-other-cc-components to pkg. Fix all files to use the new paths (#88)
This commit is contained in:
113
pkg/ccLogger/cclogger.go
Normal file
113
pkg/ccLogger/cclogger.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package cclogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
globalDebug = false
|
||||
stdout = os.Stdout
|
||||
stderr = os.Stderr
|
||||
debugLog *log.Logger = nil
|
||||
infoLog *log.Logger = nil
|
||||
errorLog *log.Logger = nil
|
||||
warnLog *log.Logger = nil
|
||||
defaultLog *log.Logger = nil
|
||||
)
|
||||
|
||||
func initLogger() {
|
||||
if debugLog == nil {
|
||||
debugLog = log.New(stderr, "DEBUG ", log.LstdFlags)
|
||||
}
|
||||
if infoLog == nil {
|
||||
infoLog = log.New(stdout, "INFO ", log.LstdFlags)
|
||||
}
|
||||
if errorLog == nil {
|
||||
errorLog = log.New(stderr, "ERROR ", log.LstdFlags)
|
||||
}
|
||||
if warnLog == nil {
|
||||
warnLog = log.New(stderr, "WARN ", log.LstdFlags)
|
||||
}
|
||||
if defaultLog == nil {
|
||||
defaultLog = log.New(stdout, "", log.LstdFlags)
|
||||
}
|
||||
}
|
||||
|
||||
func Print(e ...interface{}) {
|
||||
initLogger()
|
||||
defaultLog.Print(e...)
|
||||
}
|
||||
|
||||
func ComponentPrint(component string, e ...interface{}) {
|
||||
initLogger()
|
||||
defaultLog.Print(fmt.Sprintf("[%s] ", component), e)
|
||||
}
|
||||
|
||||
func Info(e ...interface{}) {
|
||||
initLogger()
|
||||
infoLog.Print(e...)
|
||||
}
|
||||
|
||||
func ComponentInfo(component string, e ...interface{}) {
|
||||
initLogger()
|
||||
infoLog.Print(fmt.Sprintf("[%s] ", component), e)
|
||||
}
|
||||
|
||||
func Debug(e ...interface{}) {
|
||||
initLogger()
|
||||
if globalDebug {
|
||||
debugLog.Print(e...)
|
||||
}
|
||||
}
|
||||
|
||||
func ComponentDebug(component string, e ...interface{}) {
|
||||
initLogger()
|
||||
if globalDebug && debugLog != nil {
|
||||
//CCComponentPrint(debugLog, component, e)
|
||||
debugLog.Print(fmt.Sprintf("[%s] ", component), e)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(e ...interface{}) {
|
||||
initLogger()
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
errorLog.Print(fmt.Sprintf("[%s:%d] ", fn, line), e)
|
||||
}
|
||||
|
||||
func ComponentError(component string, e ...interface{}) {
|
||||
initLogger()
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
errorLog.Print(fmt.Sprintf("[%s|%s:%d] ", component, fn, line), e)
|
||||
}
|
||||
|
||||
func SetDebug() {
|
||||
globalDebug = true
|
||||
initLogger()
|
||||
}
|
||||
|
||||
func SetOutput(filename string) {
|
||||
if filename == "stderr" {
|
||||
if stderr != os.Stderr && stderr != os.Stdout {
|
||||
stderr.Close()
|
||||
}
|
||||
stderr = os.Stderr
|
||||
} else if filename == "stdout" {
|
||||
if stderr != os.Stderr && stderr != os.Stdout {
|
||||
stderr.Close()
|
||||
}
|
||||
stderr = os.Stdout
|
||||
} else {
|
||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
stderr = file
|
||||
}
|
||||
}
|
||||
debugLog = nil
|
||||
errorLog = nil
|
||||
warnLog = nil
|
||||
initLogger()
|
||||
}
|
57
pkg/ccMetric/README.md
Normal file
57
pkg/ccMetric/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# ClusterCockpit metrics
|
||||
|
||||
As described in the [ClusterCockpit specifications](https://github.com/ClusterCockpit/cc-specifications), the whole ClusterCockpit stack uses metrics in the InfluxDB line protocol format. This is also the input and output format for the ClusterCockpit Metric Collector but internally it uses an extended format while processing, named CCMetric.
|
||||
|
||||
It is basically a copy of the [InfluxDB line protocol](https://github.com/influxdata/line-protocol) `MutableMetric` interface with one extension. Besides the tags and fields, it contains a list of meta information (re-using the `Tag` structure of the original protocol):
|
||||
|
||||
```golang
|
||||
type ccMetric struct {
|
||||
name string // Measurement name
|
||||
meta map[string]string // map of meta data tags
|
||||
tags map[string]string // map of of tags
|
||||
fields map[string]interface{} // map of of fields
|
||||
tm time.Time // timestamp
|
||||
}
|
||||
|
||||
type CCMetric interface {
|
||||
ToPoint(metaAsTags map[string]bool) *write.Point // Generate influxDB point for data type ccMetric
|
||||
ToLineProtocol(metaAsTags map[string]bool) string // Generate influxDB line protocol for data type ccMetric
|
||||
String() string // Return line-protocol like string
|
||||
|
||||
Name() string // Get metric name
|
||||
SetName(name string) // Set metric name
|
||||
|
||||
Time() time.Time // Get timestamp
|
||||
SetTime(t time.Time) // Set timestamp
|
||||
|
||||
Tags() map[string]string // Map of tags
|
||||
AddTag(key, value string) // Add a tag
|
||||
GetTag(key string) (value string, ok bool) // Get a tag by its key
|
||||
HasTag(key string) (ok bool) // Check if a tag key is present
|
||||
RemoveTag(key string) // Remove a tag by its key
|
||||
|
||||
Meta() map[string]string // Map of meta data tags
|
||||
AddMeta(key, value string) // Add a meta data tag
|
||||
GetMeta(key string) (value string, ok bool) // Get a meta data tab addressed by its key
|
||||
HasMeta(key string) (ok bool) // Check if a meta data key is present
|
||||
RemoveMeta(key string) // Remove a meta data tag by its key
|
||||
|
||||
Fields() map[string]interface{} // Map of fields
|
||||
AddField(key string, value interface{}) // Add a field
|
||||
GetField(key string) (value interface{}, ok bool) // Get a field addressed by its key
|
||||
HasField(key string) (ok bool) // Check if a field key is present
|
||||
RemoveField(key string) // Remove a field addressed by its key
|
||||
}
|
||||
|
||||
func New(name string, tags map[string]string, meta map[string]string, fields map[string]interface{}, tm time.Time) (CCMetric, error)
|
||||
func FromMetric(other CCMetric) CCMetric
|
||||
func FromInfluxMetric(other lp.Metric) CCMetric
|
||||
```
|
||||
|
||||
The `CCMetric` interface provides the same functions as the `MutableMetric` like `{Add, Get, Remove, Has}{Tag, Field}` and additionally provides `{Add, Get, Remove, Has}Meta`.
|
||||
|
||||
The InfluxDB protocol creates a new metric with `influx.New(name, tags, fields, time)` while CCMetric uses `ccMetric.New(name, tags, meta, fields, time)` where `tags` and `meta` are both of type `map[string]string`.
|
||||
|
||||
You can copy a CCMetric with `FromMetric(other CCMetric) CCMetric`. If you get an `influx.Metric` from a function, like the line protocol parser, you can use `FromInfluxMetric(other influx.Metric) CCMetric` to get a CCMetric out of it (see `NatsReceiver` for an example).
|
||||
|
||||
Although the [cc-specifications](https://github.com/ClusterCockpit/cc-specifications/blob/master/interfaces/lineprotocol/README.md) defines that there is only a `value` field for the metric value, the CCMetric still can have multiple values similar to the InfluxDB line protocol.
|
368
pkg/ccMetric/ccMetric.go
Normal file
368
pkg/ccMetric/ccMetric.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package ccmetric
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
write "github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||
lp "github.com/influxdata/line-protocol" // MIT license
|
||||
)
|
||||
|
||||
// Most functions are derived from github.com/influxdata/line-protocol/metric.go
|
||||
// The metric type is extended with an extra meta information list re-using the Tag
|
||||
// type.
|
||||
//
|
||||
// See: https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/
|
||||
type ccMetric struct {
|
||||
name string // Measurement name
|
||||
meta map[string]string // map of meta data tags
|
||||
tags map[string]string // map of of tags
|
||||
fields map[string]interface{} // map of of fields
|
||||
tm time.Time // timestamp
|
||||
}
|
||||
|
||||
// ccMetric access functions
|
||||
type CCMetric interface {
|
||||
ToPoint(metaAsTags map[string]bool) *write.Point // Generate influxDB point for data type ccMetric
|
||||
ToLineProtocol(metaAsTags map[string]bool) string // Generate influxDB line protocol for data type ccMetric
|
||||
|
||||
Name() string // Get metric name
|
||||
SetName(name string) // Set metric name
|
||||
|
||||
Time() time.Time // Get timestamp
|
||||
SetTime(t time.Time) // Set timestamp
|
||||
|
||||
Tags() map[string]string // Map of tags
|
||||
AddTag(key, value string) // Add a tag
|
||||
GetTag(key string) (value string, ok bool) // Get a tag by its key
|
||||
HasTag(key string) (ok bool) // Check if a tag key is present
|
||||
RemoveTag(key string) // Remove a tag by its key
|
||||
|
||||
Meta() map[string]string // Map of meta data tags
|
||||
AddMeta(key, value string) // Add a meta data tag
|
||||
GetMeta(key string) (value string, ok bool) // Get a meta data tab addressed by its key
|
||||
HasMeta(key string) (ok bool) // Check if a meta data key is present
|
||||
RemoveMeta(key string) // Remove a meta data tag by its key
|
||||
|
||||
Fields() map[string]interface{} // Map of fields
|
||||
AddField(key string, value interface{}) // Add a field
|
||||
GetField(key string) (value interface{}, ok bool) // Get a field addressed by its key
|
||||
HasField(key string) (ok bool) // Check if a field key is present
|
||||
RemoveField(key string) // Remove a field addressed by its key
|
||||
String() string // Return line-protocol like string
|
||||
}
|
||||
|
||||
// String implements the stringer interface for data type ccMetric
|
||||
func (m *ccMetric) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Name: %s, Tags: %+v, Meta: %+v, fields: %+v, Timestamp: %d",
|
||||
m.name, m.tags, m.meta, m.fields, m.tm.UnixNano(),
|
||||
)
|
||||
}
|
||||
|
||||
// ToLineProtocol generates influxDB line protocol for data type ccMetric
|
||||
func (m *ccMetric) ToPoint(metaAsTags map[string]bool) (p *write.Point) {
|
||||
p = influxdb2.NewPoint(m.name, m.tags, m.fields, m.tm)
|
||||
for key, ok1 := range metaAsTags {
|
||||
if val, ok2 := m.GetMeta(key); ok1 && ok2 {
|
||||
p.AddTag(key, val)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// ToLineProtocol generates influxDB line protocol for data type ccMetric
|
||||
func (m *ccMetric) ToLineProtocol(metaAsTags map[string]bool) string {
|
||||
|
||||
return write.PointToLineProtocol(
|
||||
m.ToPoint(metaAsTags),
|
||||
time.Nanosecond,
|
||||
)
|
||||
}
|
||||
|
||||
// Name returns the measurement name
|
||||
func (m *ccMetric) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// SetName sets the measurement name
|
||||
func (m *ccMetric) SetName(name string) {
|
||||
m.name = name
|
||||
}
|
||||
|
||||
// Time returns timestamp
|
||||
func (m *ccMetric) Time() time.Time {
|
||||
return m.tm
|
||||
}
|
||||
|
||||
// SetTime sets the timestamp
|
||||
func (m *ccMetric) SetTime(t time.Time) {
|
||||
m.tm = t
|
||||
}
|
||||
|
||||
// Tags returns the the list of tags as key-value-mapping
|
||||
func (m *ccMetric) Tags() map[string]string {
|
||||
return m.tags
|
||||
}
|
||||
|
||||
// AddTag adds a tag (consisting of key and value) to the map of tags
|
||||
func (m *ccMetric) AddTag(key, value string) {
|
||||
m.tags[key] = value
|
||||
}
|
||||
|
||||
// GetTag returns the tag with tag's key equal to <key>
|
||||
func (m *ccMetric) GetTag(key string) (string, bool) {
|
||||
value, ok := m.tags[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// HasTag checks if a tag with key equal to <key> is present in the list of tags
|
||||
func (m *ccMetric) HasTag(key string) bool {
|
||||
_, ok := m.tags[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// RemoveTag removes the tag with tag's key equal to <key>
|
||||
func (m *ccMetric) RemoveTag(key string) {
|
||||
delete(m.tags, key)
|
||||
}
|
||||
|
||||
// Meta returns the meta data tags as key-value mapping
|
||||
func (m *ccMetric) Meta() map[string]string {
|
||||
return m.meta
|
||||
}
|
||||
|
||||
// AddMeta adds a meta data tag (consisting of key and value) to the map of meta data tags
|
||||
func (m *ccMetric) AddMeta(key, value string) {
|
||||
m.meta[key] = value
|
||||
}
|
||||
|
||||
// GetMeta returns the meta data tag with meta data's key equal to <key>
|
||||
func (m *ccMetric) GetMeta(key string) (string, bool) {
|
||||
value, ok := m.meta[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// HasMeta checks if a meta data tag with meta data's key equal to <key> is present in the map of meta data tags
|
||||
func (m *ccMetric) HasMeta(key string) bool {
|
||||
_, ok := m.meta[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// RemoveMeta removes the meta data tag with tag's key equal to <key>
|
||||
func (m *ccMetric) RemoveMeta(key string) {
|
||||
delete(m.meta, key)
|
||||
}
|
||||
|
||||
// Fields returns the list of fields as key-value-mapping
|
||||
func (m *ccMetric) Fields() map[string]interface{} {
|
||||
return m.fields
|
||||
}
|
||||
|
||||
// AddField adds a field (consisting of key and value) to the map of fields
|
||||
func (m *ccMetric) AddField(key string, value interface{}) {
|
||||
m.fields[key] = value
|
||||
}
|
||||
|
||||
// GetField returns the field with field's key equal to <key>
|
||||
func (m *ccMetric) GetField(key string) (interface{}, bool) {
|
||||
v, ok := m.fields[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// HasField checks if a field with field's key equal to <key> is present in the map of fields
|
||||
func (m *ccMetric) HasField(key string) bool {
|
||||
_, ok := m.fields[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// RemoveField removes the field with field's key equal to <key>
|
||||
// from the map of fields
|
||||
func (m *ccMetric) RemoveField(key string) {
|
||||
delete(m.fields, key)
|
||||
}
|
||||
|
||||
// New creates a new measurement point
|
||||
func New(
|
||||
name string,
|
||||
tags map[string]string,
|
||||
meta map[string]string,
|
||||
fields map[string]interface{},
|
||||
tm time.Time,
|
||||
) (CCMetric, error) {
|
||||
m := &ccMetric{
|
||||
name: name,
|
||||
tags: make(map[string]string, len(tags)),
|
||||
meta: make(map[string]string, len(meta)),
|
||||
fields: make(map[string]interface{}, len(fields)),
|
||||
tm: tm,
|
||||
}
|
||||
|
||||
// deep copy tags, meta data tags and fields
|
||||
for k, v := range tags {
|
||||
m.tags[k] = v
|
||||
}
|
||||
for k, v := range meta {
|
||||
m.meta[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
v := convertField(v)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
m.fields[k] = v
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// FromMetric copies the metric <other>
|
||||
func FromMetric(other CCMetric) CCMetric {
|
||||
otags := other.Tags()
|
||||
ometa := other.Meta()
|
||||
ofields := other.Fields()
|
||||
m := &ccMetric{
|
||||
name: other.Name(),
|
||||
tags: make(map[string]string, len(otags)),
|
||||
meta: make(map[string]string, len(ometa)),
|
||||
fields: make(map[string]interface{}, len(ofields)),
|
||||
tm: other.Time(),
|
||||
}
|
||||
|
||||
// deep copy tags, meta data tags and fields
|
||||
for key, value := range otags {
|
||||
m.tags[key] = value
|
||||
}
|
||||
for key, value := range ometa {
|
||||
m.meta[key] = value
|
||||
}
|
||||
for key, value := range ofields {
|
||||
m.fields[key] = value
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FromInfluxMetric copies the influxDB line protocol metric <other>
|
||||
func FromInfluxMetric(other lp.Metric) CCMetric {
|
||||
m := &ccMetric{
|
||||
name: other.Name(),
|
||||
tags: make(map[string]string),
|
||||
meta: make(map[string]string),
|
||||
fields: make(map[string]interface{}),
|
||||
tm: other.Time(),
|
||||
}
|
||||
|
||||
// deep copy tags and fields
|
||||
for _, otherTag := range other.TagList() {
|
||||
m.tags[otherTag.Key] = otherTag.Value
|
||||
}
|
||||
for _, otherField := range other.FieldList() {
|
||||
m.fields[otherField.Key] = otherField.Value
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// convertField converts data types of fields by the following schemata:
|
||||
// *float32, *float64, float32, float64 -> float64
|
||||
// *int, *int8, *int16, *int32, *int64, int, int8, int16, int32, int64 -> int64
|
||||
// *uint, *uint8, *uint16, *uint32, *uint64, uint, uint8, uint16, uint32, uint64 -> uint64
|
||||
// *[]byte, *string, []byte, string -> string
|
||||
// *bool, bool -> bool
|
||||
func convertField(v interface{}) interface{} {
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return v
|
||||
case int64:
|
||||
return v
|
||||
case string:
|
||||
return v
|
||||
case bool:
|
||||
return v
|
||||
case int:
|
||||
return int64(v)
|
||||
case uint:
|
||||
return uint64(v)
|
||||
case uint64:
|
||||
return uint64(v)
|
||||
case []byte:
|
||||
return string(v)
|
||||
case int32:
|
||||
return int64(v)
|
||||
case int16:
|
||||
return int64(v)
|
||||
case int8:
|
||||
return int64(v)
|
||||
case uint32:
|
||||
return uint64(v)
|
||||
case uint16:
|
||||
return uint64(v)
|
||||
case uint8:
|
||||
return uint64(v)
|
||||
case float32:
|
||||
return float64(v)
|
||||
case *float64:
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
case *int64:
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
case *string:
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
case *bool:
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
case *int:
|
||||
if v != nil {
|
||||
return int64(*v)
|
||||
}
|
||||
case *uint:
|
||||
if v != nil {
|
||||
return uint64(*v)
|
||||
}
|
||||
case *uint64:
|
||||
if v != nil {
|
||||
return uint64(*v)
|
||||
}
|
||||
case *[]byte:
|
||||
if v != nil {
|
||||
return string(*v)
|
||||
}
|
||||
case *int32:
|
||||
if v != nil {
|
||||
return int64(*v)
|
||||
}
|
||||
case *int16:
|
||||
if v != nil {
|
||||
return int64(*v)
|
||||
}
|
||||
case *int8:
|
||||
if v != nil {
|
||||
return int64(*v)
|
||||
}
|
||||
case *uint32:
|
||||
if v != nil {
|
||||
return uint64(*v)
|
||||
}
|
||||
case *uint16:
|
||||
if v != nil {
|
||||
return uint64(*v)
|
||||
}
|
||||
case *uint8:
|
||||
if v != nil {
|
||||
return uint64(*v)
|
||||
}
|
||||
case *float32:
|
||||
if v != nil {
|
||||
return float64(*v)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
469
pkg/ccTopology/ccTopology.go
Normal file
469
pkg/ccTopology/ccTopology.go
Normal file
@@ -0,0 +1,469 @@
|
||||
package ccTopology
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
cclogger "github.com/ClusterCockpit/cc-metric-collector/pkg/ccLogger"
|
||||
)
|
||||
|
||||
const SYSFS_NUMABASE = `/sys/devices/system/node`
|
||||
const SYSFS_CPUBASE = `/sys/devices/system/cpu`
|
||||
const PROCFS_CPUINFO = `/proc/cpuinfo`
|
||||
|
||||
// intArrayContains scans an array of ints if the value str is present in the array
|
||||
// If the specified value is found, the corresponding array index is returned.
|
||||
// The bool value is used to signal success or failure
|
||||
func intArrayContains(array []int, str int) (int, bool) {
|
||||
for i, a := range array {
|
||||
if a == str {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// Used internally for sysfs file reads
|
||||
func fileToInt(path string) int {
|
||||
buffer, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
cclogger.ComponentError("ccTopology", "Reading", path, ":", err.Error())
|
||||
return -1
|
||||
}
|
||||
sbuffer := strings.Replace(string(buffer), "\n", "", -1)
|
||||
var id int64
|
||||
//_, err = fmt.Scanf("%d", sbuffer, &id)
|
||||
id, err = strconv.ParseInt(sbuffer, 10, 32)
|
||||
if err != nil {
|
||||
cclogger.ComponentError("ccTopology", "Parsing", path, ":", sbuffer, err.Error())
|
||||
return -1
|
||||
}
|
||||
return int(id)
|
||||
}
|
||||
|
||||
// Get list of CPU socket IDs
|
||||
func SocketList() []int {
|
||||
buffer, err := ioutil.ReadFile(string(PROCFS_CPUINFO))
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
ll := strings.Split(string(buffer), "\n")
|
||||
packs := make([]int, 0)
|
||||
for _, line := range ll {
|
||||
if strings.HasPrefix(line, "physical id") {
|
||||
lv := strings.Fields(line)
|
||||
id, err := strconv.ParseInt(lv[3], 10, 32)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return packs
|
||||
}
|
||||
_, found := intArrayContains(packs, int(id))
|
||||
if !found {
|
||||
packs = append(packs, int(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return packs
|
||||
}
|
||||
|
||||
// Get list of hardware thread IDs in the order of listing in /proc/cpuinfo
|
||||
func HwthreadList() []int {
|
||||
buffer, err := ioutil.ReadFile(string(PROCFS_CPUINFO))
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
ll := strings.Split(string(buffer), "\n")
|
||||
cpulist := make([]int, 0)
|
||||
for _, line := range ll {
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
lv := strings.Fields(line)
|
||||
id, err := strconv.ParseInt(lv[2], 10, 32)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return cpulist
|
||||
}
|
||||
_, found := intArrayContains(cpulist, int(id))
|
||||
if !found {
|
||||
cpulist = append(cpulist, int(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return cpulist
|
||||
}
|
||||
|
||||
// Get list of hardware thread IDs in the order of listing in /proc/cpuinfo
|
||||
// Deprecated! Use HwthreadList()
|
||||
func CpuList() []int {
|
||||
return HwthreadList()
|
||||
}
|
||||
|
||||
// Get list of CPU core IDs in the order of listing in /proc/cpuinfo
|
||||
func CoreList() []int {
|
||||
buffer, err := ioutil.ReadFile(string(PROCFS_CPUINFO))
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
ll := strings.Split(string(buffer), "\n")
|
||||
corelist := make([]int, 0)
|
||||
for _, line := range ll {
|
||||
if strings.HasPrefix(line, "core id") {
|
||||
lv := strings.Fields(line)
|
||||
id, err := strconv.ParseInt(lv[3], 10, 32)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return corelist
|
||||
}
|
||||
_, found := intArrayContains(corelist, int(id))
|
||||
if !found {
|
||||
corelist = append(corelist, int(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return corelist
|
||||
}
|
||||
|
||||
// Get list of NUMA node IDs
|
||||
func NumaNodeList() []int {
|
||||
numaList := make([]int, 0)
|
||||
globPath := filepath.Join(string(SYSFS_NUMABASE), "node*")
|
||||
regexPath := filepath.Join(string(SYSFS_NUMABASE), "node(\\d+)")
|
||||
regex := regexp.MustCompile(regexPath)
|
||||
files, err := filepath.Glob(globPath)
|
||||
if err != nil {
|
||||
cclogger.ComponentError("CCTopology", "NumaNodeList", err.Error())
|
||||
}
|
||||
for _, f := range files {
|
||||
if !regex.MatchString(f) {
|
||||
continue
|
||||
}
|
||||
finfo, err := os.Lstat(f)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !finfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
matches := regex.FindStringSubmatch(f)
|
||||
if len(matches) == 2 {
|
||||
id, err := strconv.Atoi(matches[1])
|
||||
if err == nil {
|
||||
if _, found := intArrayContains(numaList, id); !found {
|
||||
numaList = append(numaList, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return numaList
|
||||
}
|
||||
|
||||
// Get list of CPU die IDs
|
||||
func DieList() []int {
|
||||
cpulist := HwthreadList()
|
||||
dielist := make([]int, 0)
|
||||
for _, c := range cpulist {
|
||||
diepath := filepath.Join(string(SYSFS_CPUBASE), fmt.Sprintf("cpu%d", c), "topology/die_id")
|
||||
dieid := fileToInt(diepath)
|
||||
if dieid > 0 {
|
||||
_, found := intArrayContains(dielist, int(dieid))
|
||||
if !found {
|
||||
dielist = append(dielist, int(dieid))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(dielist) > 0 {
|
||||
return dielist
|
||||
}
|
||||
return SocketList()
|
||||
}
|
||||
|
||||
// Get list of specified type using the naming format inside ClusterCockpit
|
||||
func GetTypeList(topology_type string) []int {
|
||||
switch topology_type {
|
||||
case "node":
|
||||
return []int{0}
|
||||
case "socket":
|
||||
return SocketList()
|
||||
case "die":
|
||||
return DieList()
|
||||
case "memoryDomain":
|
||||
return NumaNodeList()
|
||||
case "core":
|
||||
return CoreList()
|
||||
case "hwthread":
|
||||
return HwthreadList()
|
||||
}
|
||||
return []int{}
|
||||
}
|
||||
|
||||
// Structure holding all information about a hardware thread
|
||||
type HwthreadEntry struct {
|
||||
Cpuid int
|
||||
SMT int
|
||||
Core int
|
||||
Socket int
|
||||
Numadomain int
|
||||
Die int
|
||||
}
|
||||
|
||||
func CpuData() []HwthreadEntry {
|
||||
|
||||
// fileToInt := func(path string) int {
|
||||
// buffer, err := ioutil.ReadFile(path)
|
||||
// if err != nil {
|
||||
// log.Print(err)
|
||||
// //cclogger.ComponentError("ccTopology", "Reading", path, ":", err.Error())
|
||||
// return -1
|
||||
// }
|
||||
// sbuffer := strings.Replace(string(buffer), "\n", "", -1)
|
||||
// var id int64
|
||||
// //_, err = fmt.Scanf("%d", sbuffer, &id)
|
||||
// id, err = strconv.ParseInt(sbuffer, 10, 32)
|
||||
// if err != nil {
|
||||
// cclogger.ComponentError("ccTopology", "Parsing", path, ":", sbuffer, err.Error())
|
||||
// return -1
|
||||
// }
|
||||
// return int(id)
|
||||
// }
|
||||
getCore := func(basepath string) int {
|
||||
return fileToInt(fmt.Sprintf("%s/core_id", basepath))
|
||||
}
|
||||
|
||||
getSocket := func(basepath string) int {
|
||||
return fileToInt(fmt.Sprintf("%s/physical_package_id", basepath))
|
||||
}
|
||||
|
||||
getDie := func(basepath string) int {
|
||||
return fileToInt(fmt.Sprintf("%s/die_id", basepath))
|
||||
}
|
||||
|
||||
getSMT := func(cpuid int, basepath string) int {
|
||||
buffer, err := ioutil.ReadFile(fmt.Sprintf("%s/thread_siblings_list", basepath))
|
||||
if err != nil {
|
||||
cclogger.ComponentError("CCTopology", "CpuData:getSMT", err.Error())
|
||||
}
|
||||
threadlist := make([]int, 0)
|
||||
sbuffer := strings.Replace(string(buffer), "\n", "", -1)
|
||||
for _, x := range strings.Split(sbuffer, ",") {
|
||||
id, err := strconv.ParseInt(x, 10, 32)
|
||||
if err != nil {
|
||||
cclogger.ComponentError("CCTopology", "CpuData:getSMT", err.Error())
|
||||
}
|
||||
threadlist = append(threadlist, int(id))
|
||||
}
|
||||
for i, x := range threadlist {
|
||||
if x == cpuid {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
getNumaDomain := func(basepath string) int {
|
||||
globPath := filepath.Join(basepath, "node*")
|
||||
regexPath := filepath.Join(basepath, "node(\\d+)")
|
||||
regex := regexp.MustCompile(regexPath)
|
||||
files, err := filepath.Glob(globPath)
|
||||
if err != nil {
|
||||
cclogger.ComponentError("CCTopology", "CpuData:getNumaDomain", err.Error())
|
||||
}
|
||||
for _, f := range files {
|
||||
finfo, err := os.Lstat(f)
|
||||
if err == nil && finfo.IsDir() {
|
||||
matches := regex.FindStringSubmatch(f)
|
||||
if len(matches) == 2 {
|
||||
id, err := strconv.Atoi(matches[1])
|
||||
if err == nil {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
clist := make([]HwthreadEntry, 0)
|
||||
for _, c := range HwthreadList() {
|
||||
clist = append(clist, HwthreadEntry{Cpuid: c})
|
||||
}
|
||||
for i, centry := range clist {
|
||||
centry.Socket = -1
|
||||
centry.Numadomain = -1
|
||||
centry.Die = -1
|
||||
centry.Core = -1
|
||||
// Set base directory for topology lookup
|
||||
cpustr := fmt.Sprintf("cpu%d", centry.Cpuid)
|
||||
base := filepath.Join("/sys/devices/system/cpu", cpustr)
|
||||
topoBase := filepath.Join(base, "topology")
|
||||
|
||||
// Lookup CPU core id
|
||||
centry.Core = getCore(topoBase)
|
||||
|
||||
// Lookup CPU socket id
|
||||
centry.Socket = getSocket(topoBase)
|
||||
|
||||
// Lookup CPU die id
|
||||
centry.Die = getDie(topoBase)
|
||||
if centry.Die < 0 {
|
||||
centry.Die = centry.Socket
|
||||
}
|
||||
|
||||
// Lookup SMT thread id
|
||||
centry.SMT = getSMT(centry.Cpuid, topoBase)
|
||||
|
||||
// Lookup NUMA domain id
|
||||
centry.Numadomain = getNumaDomain(base)
|
||||
|
||||
// Update values in output list
|
||||
clist[i] = centry
|
||||
}
|
||||
return clist
|
||||
}
|
||||
|
||||
// Structure holding basic information about a CPU
|
||||
type CpuInformation struct {
|
||||
NumHWthreads int
|
||||
SMTWidth int
|
||||
NumSockets int
|
||||
NumDies int
|
||||
NumCores int
|
||||
NumNumaDomains int
|
||||
}
|
||||
|
||||
// Get basic information about the CPU
|
||||
func CpuInfo() CpuInformation {
|
||||
var c CpuInformation
|
||||
|
||||
smtList := make([]int, 0)
|
||||
numaList := make([]int, 0)
|
||||
dieList := make([]int, 0)
|
||||
socketList := make([]int, 0)
|
||||
coreList := make([]int, 0)
|
||||
cdata := CpuData()
|
||||
for _, d := range cdata {
|
||||
if _, ok := intArrayContains(smtList, d.SMT); !ok {
|
||||
smtList = append(smtList, d.SMT)
|
||||
}
|
||||
if _, ok := intArrayContains(numaList, d.Numadomain); !ok {
|
||||
numaList = append(numaList, d.Numadomain)
|
||||
}
|
||||
if _, ok := intArrayContains(dieList, d.Die); !ok {
|
||||
dieList = append(dieList, d.Die)
|
||||
}
|
||||
if _, ok := intArrayContains(socketList, d.Socket); !ok {
|
||||
socketList = append(socketList, d.Socket)
|
||||
}
|
||||
if _, ok := intArrayContains(coreList, d.Core); !ok {
|
||||
coreList = append(coreList, d.Core)
|
||||
}
|
||||
}
|
||||
c.NumNumaDomains = len(numaList)
|
||||
c.SMTWidth = len(smtList)
|
||||
c.NumDies = len(dieList)
|
||||
c.NumCores = len(coreList)
|
||||
c.NumSockets = len(socketList)
|
||||
c.NumHWthreads = len(cdata)
|
||||
return c
|
||||
}
|
||||
|
||||
// Get the CPU socket ID for a given hardware thread ID
|
||||
func GetHwthreadSocket(cpuid int) int {
|
||||
cdata := CpuData()
|
||||
for _, d := range cdata {
|
||||
if d.Cpuid == cpuid {
|
||||
return d.Socket
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Get the NUMA node ID for a given hardware thread ID
|
||||
func GetHwthreadNumaDomain(cpuid int) int {
|
||||
cdata := CpuData()
|
||||
for _, d := range cdata {
|
||||
if d.Cpuid == cpuid {
|
||||
return d.Numadomain
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Get the CPU die ID for a given hardware thread ID
|
||||
func GetHwthreadDie(cpuid int) int {
|
||||
cdata := CpuData()
|
||||
for _, d := range cdata {
|
||||
if d.Cpuid == cpuid {
|
||||
return d.Die
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Get the CPU core ID for a given hardware thread ID
|
||||
func GetHwthreadCore(cpuid int) int {
|
||||
cdata := CpuData()
|
||||
for _, d := range cdata {
|
||||
if d.Cpuid == cpuid {
|
||||
return d.Core
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Get the all hardware thread ID associated with a CPU socket
|
||||
func GetSocketHwthreads(socket int) []int {
|
||||
all := CpuData()
|
||||
cpulist := make([]int, 0)
|
||||
for _, d := range all {
|
||||
if d.Socket == socket {
|
||||
cpulist = append(cpulist, d.Cpuid)
|
||||
}
|
||||
}
|
||||
return cpulist
|
||||
}
|
||||
|
||||
// Get the all hardware thread ID associated with a NUMA node
|
||||
func GetNumaDomainHwthreads(domain int) []int {
|
||||
all := CpuData()
|
||||
cpulist := make([]int, 0)
|
||||
for _, d := range all {
|
||||
if d.Numadomain == domain {
|
||||
cpulist = append(cpulist, d.Cpuid)
|
||||
}
|
||||
}
|
||||
return cpulist
|
||||
}
|
||||
|
||||
// Get the all hardware thread ID associated with a CPU die
|
||||
func GetDieHwthreads(die int) []int {
|
||||
all := CpuData()
|
||||
cpulist := make([]int, 0)
|
||||
for _, d := range all {
|
||||
if d.Die == die {
|
||||
cpulist = append(cpulist, d.Cpuid)
|
||||
}
|
||||
}
|
||||
return cpulist
|
||||
}
|
||||
|
||||
// Get the all hardware thread ID associated with a CPU core
|
||||
func GetCoreHwthreads(core int) []int {
|
||||
all := CpuData()
|
||||
cpulist := make([]int, 0)
|
||||
for _, d := range all {
|
||||
if d.Core == core {
|
||||
cpulist = append(cpulist, d.Cpuid)
|
||||
}
|
||||
}
|
||||
return cpulist
|
||||
}
|
37
pkg/multiChanTicker/README.md
Normal file
37
pkg/multiChanTicker/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# MultiChanTicker
|
||||
|
||||
The idea of this ticker is to multiply the output channels. The original Golang `time.Ticker` provides only a single output channel, so the signal can only be received by a single other class. This ticker allows to add multiple channels which get all notified about the time tick.
|
||||
|
||||
```golang
|
||||
type MultiChanTicker interface {
|
||||
Init(duration time.Duration)
|
||||
AddChannel(chan time.Time)
|
||||
}
|
||||
```
|
||||
|
||||
The MultiChanTicker is created similarly to the common `time.Ticker`:
|
||||
|
||||
```golang
|
||||
NewTicker(duration time.Duration) MultiChanTicker
|
||||
```
|
||||
|
||||
Afterwards, you can add channels:
|
||||
|
||||
```golang
|
||||
t := MultiChanTicker(duration)
|
||||
c1 := make(chan time.Time)
|
||||
c2 := make(chan time.Time)
|
||||
t.AddChannel(c1)
|
||||
t.AddChannel(c2)
|
||||
|
||||
for {
|
||||
select {
|
||||
case t1 := <- c1:
|
||||
log.Print(t1)
|
||||
case t2 := <- c2:
|
||||
log.Print(t2)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The result should be the same `time.Time` output in both channels, notified "simultaneously".
|
64
pkg/multiChanTicker/multiChanTicker.go
Normal file
64
pkg/multiChanTicker/multiChanTicker.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package multiChanTicker
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
cclog "github.com/ClusterCockpit/cc-metric-collector/pkg/ccLogger"
|
||||
)
|
||||
|
||||
type multiChanTicker struct {
|
||||
ticker *time.Ticker
|
||||
channels []chan time.Time
|
||||
done chan bool
|
||||
}
|
||||
|
||||
type MultiChanTicker interface {
|
||||
Init(duration time.Duration)
|
||||
AddChannel(chan time.Time)
|
||||
Close()
|
||||
}
|
||||
|
||||
func (t *multiChanTicker) Init(duration time.Duration) {
|
||||
t.ticker = time.NewTicker(duration)
|
||||
t.done = make(chan bool)
|
||||
go func() {
|
||||
done := func() {
|
||||
close(t.done)
|
||||
cclog.ComponentDebug("MultiChanTicker", "DONE")
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-t.done:
|
||||
done()
|
||||
return
|
||||
case ts := <-t.ticker.C:
|
||||
cclog.ComponentDebug("MultiChanTicker", "Tick", ts)
|
||||
for _, c := range t.channels {
|
||||
select {
|
||||
case <-t.done:
|
||||
done()
|
||||
return
|
||||
case c <- ts:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *multiChanTicker) AddChannel(channel chan time.Time) {
|
||||
t.channels = append(t.channels, channel)
|
||||
}
|
||||
|
||||
func (t *multiChanTicker) Close() {
|
||||
cclog.ComponentDebug("MultiChanTicker", "CLOSE")
|
||||
t.done <- true
|
||||
// wait for close of channel t.done
|
||||
<-t.done
|
||||
}
|
||||
|
||||
func NewTicker(duration time.Duration) MultiChanTicker {
|
||||
t := &multiChanTicker{}
|
||||
t.Init(duration)
|
||||
return t
|
||||
}
|
Reference in New Issue
Block a user