mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2024-12-25 23:19:06 +01:00
Updated README.md for collectors. Not finished
This commit is contained in:
parent
325d97df96
commit
734d9e5b8f
@ -5,111 +5,351 @@ The base class/configuration is located in `metricCollector.go`.
|
|||||||
|
|
||||||
# Collectors
|
# Collectors
|
||||||
|
|
||||||
* `memstatMetric.go`: Reads `/proc/meminfo` to calculate the **node** metric `mem_used`
|
* `memstatMetric.go`: Reads `/proc/meminfo` to calculate **node** metrics. It also combines values to the metric `mem_used`
|
||||||
* `loadavgMetric.go`: Reads `/proc/loadavg` and submits the **node** metrics:
|
* `loadavgMetric.go`: Reads `/proc/loadavg` and submits **node** metrics:
|
||||||
* `load_one`
|
* `netstatMetric.go`: Reads `/proc/net/dev` and submits for all network devices as the **node** metrics.
|
||||||
* `load_five`
|
* `lustreMetric.go`: Reads Lustre's stats files and submits **node** metrics:
|
||||||
* `load_fifteen`
|
* `infinibandMetric.go`: Reads InfiniBand metrics. It uses the `perfquery` command to read the **node** metrics but can fallback to sysfs counters in case `perfquery` does not work.
|
||||||
* `netstatMetric.go`: Reads `/proc/net/dev` and submits for all network devices (except loopback `lo`) the **node** metrics:
|
* `likwidMetric.go`: Reads hardware performance events using LIKWID. It submits **socket** and **cpu** metrics
|
||||||
* `<dev>_bytes_in`
|
|
||||||
* `<dev>_bytes_out`
|
|
||||||
* `<dev>_pkts_in`
|
|
||||||
* `<dev>_pkts_out`
|
|
||||||
* `lustreMetric.go`: Reads Lustre's stats file `/proc/fs/lustre/llite/lnec-XXXXXX/stats` and submits the **node** metrics:
|
|
||||||
* `read_bytes`
|
|
||||||
* `read_requests`
|
|
||||||
* `write_bytes`
|
|
||||||
* `write_bytes`
|
|
||||||
* `open`
|
|
||||||
* `close`
|
|
||||||
* `getattr`
|
|
||||||
* `setattr`
|
|
||||||
* `statfs`
|
|
||||||
* `inode_permission`
|
|
||||||
* `infinibandMetric.go`: Reads InfiniBand LID from `/sys/class/infiniband/mlx4_0/ports/1/lid` and uses the `perfquery` command to read the **node** metrics:
|
|
||||||
* `ib_recv`
|
|
||||||
* `ib_xmit`
|
|
||||||
* `likwidMetric.go`: Reads hardware performance events using LIKWID. It submits **socket** and **cpu** metrics:
|
|
||||||
* `mem_bw` (socket)
|
|
||||||
* `power` (socket, Sum of RAPL domains PKG and DRAM)
|
|
||||||
* `flops_dp` (cpu)
|
|
||||||
* `flops_sp` (cpu)
|
|
||||||
* `flops_any` (cpu, `2*flops_dp + flops_sp`)
|
|
||||||
* `cpi` (cpu)
|
|
||||||
* `clock` (cpu)
|
|
||||||
* `cpustatMetric.go`: Read CPU specific values from `/proc/stat`
|
* `cpustatMetric.go`: Read CPU specific values from `/proc/stat`
|
||||||
* `topprocsMetric.go`: Reads the Top5 processes by their CPU usage
|
* `topprocsMetric.go`: Reads the TopX processes by their CPU usage. X is configurable
|
||||||
* `nvidiaMetric.go`: Read data about Nvidia GPUs using the NVML library
|
* `nvidiaMetric.go`: Read data about Nvidia GPUs using the NVML library
|
||||||
* `tempMetric.go`: Read temperature data from `/sys/class/hwmon/hwmon*`
|
* `tempMetric.go`: Read temperature data from `/sys/class/hwmon/hwmon*`
|
||||||
* `ipmiMetric.go`: Collect data from `ipmitool` or as fallback `ipmi-sensors`
|
* `ipmiMetric.go`: Collect data from `ipmitool` or as fallback `ipmi-sensors`
|
||||||
|
* `customCmdMetric.go`: Run commands or read files and submit the output (output has to be in InfluxDB line protocol!)
|
||||||
|
|
||||||
If any of the collectors cannot be initialized, it is excluded from all further reads. Like if the Lustre stat file is not a valid path, no Lustre specific metrics will be recorded.
|
If any of the collectors cannot be initialized, it is excluded from all further reads. Like if the Lustre stat file is not a valid path, no Lustre specific metrics will be recorded.
|
||||||
|
|
||||||
# InfiniBand collector
|
# Collector configuration
|
||||||
The InfiniBand collector requires the LID file to read the data. It has to be configured in the collector itself (`LIDFILE` in `infinibandMetric.go`)
|
|
||||||
|
|
||||||
# Lustre collector
|
```json
|
||||||
The Lustre collector requires the path to the Lustre stats file. It has to be configured in the collector itself (`LUSTREFILE` in `lustreMetric.go`)
|
"collectors": [
|
||||||
|
"tempstat"
|
||||||
# Temperature collector
|
],
|
||||||
This is optional configuration of the temperature collector. On multi-socket system there are multiple hwmon devices, one for each CPU socket but there is no field to determine which hwmon device corresponds to which CPU socket. After you determined the mapping, you can add it to the `tag_override` map in the collector.
|
"collect_config": {
|
||||||
|
"tempstat": {
|
||||||
# LIKWID collector
|
"tag_override": {
|
||||||
The `likwidMetric.go` requires preparation steps. For this, the `Makefile` can be used.
|
"hwmon0" : {
|
||||||
|
"type" : "socket",
|
||||||
There two ways to configure the LIKWID build: use a central installation of LIKWID or build a fresh copy. This can be controlled with `CENTRAL_INSTALL = <true|false>`.
|
"type-id" : "0"
|
||||||
|
},
|
||||||
If `CENTRAL_INSTALL = true`:
|
"hwmon1" : {
|
||||||
* Set the `LIKWID_BASE` to the base folder of LIKWID (try `echo $(realpath $(dirname $(which likwid-topology))/..)`)
|
"type" : "socket",
|
||||||
* Set the `LIKWID_VERSION` to a related LIKWID version. At least similar minor release 5.0.x or 5.1.x.
|
"type-id" : "1"
|
||||||
|
}
|
||||||
If `CENTRAL_INSTALL = false`:
|
}
|
||||||
* Version of LIKWID in `LIKWID_VERSION` to download from official FTP server
|
}
|
||||||
* Target user for LIKWID's accessdaemon in `DAEMON_USER`. The user has to have enough permissions to read the `msr` and `pci` device files
|
}
|
||||||
* Target group for LIKWID's accessdaemon in `DAEMON_GROUP`
|
|
||||||
* **No** need to change `LIKWID_BASE`!
|
|
||||||
|
|
||||||
Calling `make` performs the following steps:
|
|
||||||
* Download LIKWID tarball
|
|
||||||
* Unpacking
|
|
||||||
* Adjusting configuration to build LIKWID as static library
|
|
||||||
* Build it
|
|
||||||
* Copy all required files into `collectors/likwid`
|
|
||||||
* If `CENTRAL_INSTALL = false`, the accessdaemon is installed with the suid bit set using `sudo` with the configured `DAEMON_USER` and `DAEMON_GROUP`.
|
|
||||||
* Adjust group path in LIKWID collector
|
|
||||||
|
|
||||||
## Custom metrics for LIKWID
|
|
||||||
The `likwidMetric.go` collector uses it's own performance group tree by copying it from the LIKWID sources. By adding groups to this directory tree, you can use them in the collector. Additionally, you have to tell the collector which group to measure and which event count or derived metric should be used.
|
|
||||||
|
|
||||||
The collector contains a hash map with the groups and metrics (reduced set of metrics):
|
|
||||||
```
|
|
||||||
var likwid_metrics = map[string][]LikwidMetric{
|
|
||||||
"MEM_DP": {LikwidMetric{name: "mem_bw", search: "Memory bandwidth [MBytes/s]", socket_scope: true}},
|
|
||||||
"FLOPS_SP": {LikwidMetric{name: "clock", search: "Clock [MHz]", socket_scope: false}},
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The collector will measure both groups `MEM_DP` and `FLOPS_SP` for `duration` seconds (global `config.json`). It matches the LIKWID name by using the `search` string and submits the value with the given `name` as field name in either the `socket` or the `cpu` metric depending on the `socket_scope` flag.
|
The configuration of the collectors in the main config files consists of two parts: active collectors (`collectors`) and collector configuration (`collect_config`). At startup, all collectors in the `collectors` list is initialized and, if successfully initialized, added to the active collectors for metric retrieval. At initialization the collector-specific configuration from the `collect_config` section is handed over. Each collector has own configuration options, check at the collector-specific section.
|
||||||
|
|
||||||
|
## `memstat`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"memstat": {
|
||||||
|
"exclude_metrics": [
|
||||||
|
"mem_used"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `memstat` collector reads data from `/proc/meminfo` and outputs a handful **node** metrics. If a metric is not required, it can be excluded from forwarding it to the sink.
|
||||||
|
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
* `mem_total`
|
||||||
|
* `mem_sreclaimable`
|
||||||
|
* `mem_slab`
|
||||||
|
* `mem_free`
|
||||||
|
* `mem_buffers`
|
||||||
|
* `mem_cached`
|
||||||
|
* `mem_available`
|
||||||
|
* `mem_shared`
|
||||||
|
* `swap_total`
|
||||||
|
* `swap_free`
|
||||||
|
* `mem_used` = `mem_total` - (`mem_free` + `mem_buffers` + `mem_cached`)
|
||||||
|
|
||||||
|
## `loadavg`
|
||||||
|
```json
|
||||||
|
"loadavg": {
|
||||||
|
"exclude_metrics": [
|
||||||
|
"proc_run"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `loadavg` collector reads data from `/proc/loadavg` and outputs a handful **node** metrics. If a metric is not required, it can be excluded from forwarding it to the sink.
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
* `load_one`
|
||||||
|
* `load_five`
|
||||||
|
* `load_fifteen`
|
||||||
|
* `proc_run`
|
||||||
|
* `proc_total`
|
||||||
|
|
||||||
|
## `netstat`
|
||||||
|
```json
|
||||||
|
"netstat": {
|
||||||
|
"exclude_devices": [
|
||||||
|
"lo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `netstat` collector reads data from `/proc/net/dev` and outputs a handful **node** metrics. If a device is not required, it can be excluded from forwarding it to the sink. Commonly the `lo` device should be excluded.
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
* `bytes_in`
|
||||||
|
* `bytes_out`
|
||||||
|
* `pkts_in`
|
||||||
|
* `pkts_out`
|
||||||
|
|
||||||
|
The device name is added as tag `device`.
|
||||||
|
|
||||||
|
|
||||||
|
## `diskstat`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"diskstat": {
|
||||||
|
"exclude_metrics": [
|
||||||
|
"read_ms"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `netstat` collector reads data from `/proc/net/dev` and outputs a handful **node** metrics. If a metric is not required, it can be excluded from forwarding it to the sink.
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
* `reads`
|
||||||
|
* `reads_merged`
|
||||||
|
* `read_sectors`
|
||||||
|
* `read_ms`
|
||||||
|
* `writes`
|
||||||
|
* `writes_merged`
|
||||||
|
* `writes_sectors`
|
||||||
|
* `writes_ms`
|
||||||
|
* `ioops`
|
||||||
|
* `ioops_ms`
|
||||||
|
* `ioops_weighted_ms`
|
||||||
|
* `discards`
|
||||||
|
* `discards_merged`
|
||||||
|
* `discards_sectors`
|
||||||
|
* `discards_ms`
|
||||||
|
* `flushes`
|
||||||
|
* `flushes_ms`
|
||||||
|
|
||||||
|
|
||||||
|
The device name is added as tag `device`.
|
||||||
|
|
||||||
|
## `cpustat`
|
||||||
|
```json
|
||||||
|
"netstat": {
|
||||||
|
"exclude_metrics": [
|
||||||
|
"cpu_idle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `cpustat` collector reads data from `/proc/stats` and outputs a handful **node** and **hwthread** metrics. If a metric is not required, it can be excluded from forwarding it to the sink.
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
* `cpu_user`
|
||||||
|
* `cpu_nice`
|
||||||
|
* `cpu_system`
|
||||||
|
* `cpu_idle`
|
||||||
|
* `cpu_iowait`
|
||||||
|
* `cpu_irq`
|
||||||
|
* `cpu_softirq`
|
||||||
|
* `cpu_steal`
|
||||||
|
* `cpu_guest`
|
||||||
|
* `cpu_guest_nice`
|
||||||
|
|
||||||
|
## `likwid`
|
||||||
|
```json
|
||||||
|
"likwid": {
|
||||||
|
"eventsets": [
|
||||||
|
{
|
||||||
|
"events": {
|
||||||
|
"FIXC1": "ACTUAL_CPU_CLOCK",
|
||||||
|
"FIXC2": "MAX_CPU_CLOCK",
|
||||||
|
"PMC0": "RETIRED_INSTRUCTIONS",
|
||||||
|
"PMC1": "CPU_CLOCKS_UNHALTED",
|
||||||
|
"PMC2": "RETIRED_SSE_AVX_FLOPS_ALL",
|
||||||
|
"PMC3": "MERGE",
|
||||||
|
"DFC0": "DRAM_CHANNEL_0",
|
||||||
|
"DFC1": "DRAM_CHANNEL_1",
|
||||||
|
"DFC2": "DRAM_CHANNEL_2",
|
||||||
|
"DFC3": "DRAM_CHANNEL_3"
|
||||||
|
},
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"name": "ipc",
|
||||||
|
"calc": "PMC0/PMC1",
|
||||||
|
"socket_scope": false,
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flops_any",
|
||||||
|
"calc": "0.000001*PMC2/time",
|
||||||
|
"socket_scope": false,
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock_mhz",
|
||||||
|
"calc": "0.000001*(FIXC1/FIXC2)/inverseClock",
|
||||||
|
"socket_scope": false,
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mem1",
|
||||||
|
"calc": "0.000001*(DFC0+DFC1+DFC2+DFC3)*64.0/time",
|
||||||
|
"socket_scope": true,
|
||||||
|
"publish": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"events": {
|
||||||
|
"DFC0": "DRAM_CHANNEL_4",
|
||||||
|
"DFC1": "DRAM_CHANNEL_5",
|
||||||
|
"DFC2": "DRAM_CHANNEL_6",
|
||||||
|
"DFC3": "DRAM_CHANNEL_7",
|
||||||
|
"PWR0": "RAPL_CORE_ENERGY",
|
||||||
|
"PWR1": "RAPL_PKG_ENERGY"
|
||||||
|
},
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"name": "pwr_core",
|
||||||
|
"calc": "PWR0/time",
|
||||||
|
"socket_scope": false,
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pwr_pkg",
|
||||||
|
"calc": "PWR1/time",
|
||||||
|
"socket_scope": true,
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mem2",
|
||||||
|
"calc": "0.000001*(DFC0+DFC1+DFC2+DFC3)*64.0/time",
|
||||||
|
"socket_scope": true,
|
||||||
|
"publish": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalmetrics": [
|
||||||
|
{
|
||||||
|
"name": "mem_bw",
|
||||||
|
"calc": "mem1+mem2",
|
||||||
|
"socket_scope": true,
|
||||||
|
"publish": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
_Example config suitable for AMD Zen3_
|
||||||
|
|
||||||
|
The `likwid` collector reads hardware performance counters at a **hwthread** and **socket** level. The configuration looks quite complicated but it is basically copy&paste from [LIKWID's performance groups](https://github.com/RRZE-HPC/likwid/tree/master/groups). The collector made multiple iterations and tried to use the performance groups but it lacked flexibility. The current way of configuration provides most flexibility.
|
||||||
|
|
||||||
|
The logic is as following: There are multiple eventsets, each consisting of a list of counters+events and a list of metrics. If you compare a common performance group with the example setting above, there is not much difference:
|
||||||
|
```
|
||||||
|
EVENTSET -> "events": {
|
||||||
|
FIXC1 ACTUAL_CPU_CLOCK -> "FIXC1": "ACTUAL_CPU_CLOCK",
|
||||||
|
FIXC2 MAX_CPU_CLOCK -> "FIXC2": "MAX_CPU_CLOCK",
|
||||||
|
PMC0 RETIRED_INSTRUCTIONS -> "PMC0" : "RETIRED_INSTRUCTIONS",
|
||||||
|
PMC1 CPU_CLOCKS_UNHALTED -> "PMC1" : "CPU_CLOCKS_UNHALTED",
|
||||||
|
PMC2 RETIRED_SSE_AVX_FLOPS_ALL -> "PMC2": "RETIRED_SSE_AVX_FLOPS_ALL",
|
||||||
|
PMC3 MERGE -> "PMC3": "MERGE",
|
||||||
|
-> }
|
||||||
|
```
|
||||||
|
|
||||||
|
The metrics are following the same procedure:
|
||||||
|
|
||||||
|
```
|
||||||
|
METRICS -> "metrics": [
|
||||||
|
IPC PMC0/PMC1 -> {
|
||||||
|
-> "name" : "IPC",
|
||||||
|
-> "calc" : "PMC0/PMC1",
|
||||||
|
-> "socket_scope": false,
|
||||||
|
-> "publish": true
|
||||||
|
-> }
|
||||||
|
-> ]
|
||||||
|
```
|
||||||
|
|
||||||
|
The `socket_scope` option tells whether it is submitted per socket or per hwthread. If a metric is only used for internal calculations, you can set `publish = false`.
|
||||||
|
|
||||||
|
Since some metrics can only be gathered in multiple measurements (like the memory bandwidth on AMD Zen3 chips), configure multiple eventsets like in the example config and use the `globalmetrics` section to combine them. **Be aware** that the combination might be misleading because the "behavior" of a metric changes over time and the multiple measurements might count different computing phases.
|
||||||
|
|
||||||
## Todos
|
## Todos
|
||||||
* Aggregate a per-hwthread metric to a socket metric if `socket_scope=true`
|
|
||||||
* Add a JSON configuration file `likwid.json` and suitable reader for the metrics and group tree path.
|
[ ] Exclude devices for `diskstat` collector
|
||||||
* Do we need separate sections for CPU architectures? (one config file for all architectures?)
|
[ ] Aggreate metrics to higher topology entity (sum hwthread metrics to socket metric, ...). Needs to be configurable
|
||||||
* How to encode postprocessing steps. There are Go packages like [eval](https://github.com/apaxa-go/eval) or [govaluate](https://github.com/Knetic/govaluate) but they seem to be not maintained anymore.
|
|
||||||
|
|
||||||
# Contributing own collectors
|
# Contributing own collectors
|
||||||
A collector reads data from any source, parses it to metrics and submits these metrics to the `metric-collector`. A collector provides three function:
|
A collector reads data from any source, parses it to metrics and submits these metrics to the `metric-collector`. A collector provides three function:
|
||||||
|
|
||||||
* `Init() error`: Initializes the collector and its data structures.
|
* `Init(config []byte) error`: Initializes the collector using the given collector-specific config in JSON.
|
||||||
* `Read(duration time.Duration) error`: Read, parse and submit data. If the collector has to measure anything for some duration, use the provided function argument `duration`
|
* `Read(duration time.Duration, out *[]lp.MutableMetric) error`: Read, parse and submit data to the `out` list. If the collector has to measure anything for some duration, use the provided function argument `duration`.
|
||||||
* `Close()`: Closes down the collector.
|
* `Close()`: Closes down the collector.
|
||||||
|
|
||||||
It is recommanded to call `setup()` in the `Init()` function as it creates the required data structures.
|
It is recommanded to call `setup()` in the `Init()` function.
|
||||||
|
|
||||||
Each collector contains data structures for the submission of metrics after calling `setup()` in `Init()`:
|
|
||||||
|
|
||||||
* `node` (`map[string]string`): Just key-value store for all metrics concerning the whole system
|
|
||||||
* `sockets` (`map[int]map[string]string`): One key-value store per CPU socket like `sockets[1]["testmetric] = 1.0` for the second socket. You can either use `len(sockets)` to get the amount of sockets or you use `SocketList()`.
|
|
||||||
* `cpus` (`map[int]map[string]string`): One key-value store per hardware thread like `cpus[12]["testmetric] = 1.0`. You can either use `len(cpus)` to get the amount of hardware threads or you use `CpuList()`.
|
|
||||||
|
|
||||||
Finally, the collector needs to be registered in the `metric-collector.go`. There is a list of collectors called `Collectors` which is a map (string -> pointer to collector). Add a new entry with a descriptive name and the new collector.
|
Finally, the collector needs to be registered in the `metric-collector.go`. There is a list of collectors called `Collectors` which is a map (string -> pointer to collector). Add a new entry with a descriptive name and the new collector.
|
||||||
|
|
||||||
|
## Sample collector
|
||||||
|
|
||||||
|
```go
|
||||||
|
package collectors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
lp "github.com/influxdata/line-protocol"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Struct for the collector-specific JSON config
|
||||||
|
type SampleCollectorConfig struct {
|
||||||
|
ExcludeMetrics []string `json:"exclude_metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SampleCollector struct {
|
||||||
|
MetricCollector
|
||||||
|
config SampleCollectorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SampleCollector) Init(config []byte) error {
|
||||||
|
m.name = "SampleCollector"
|
||||||
|
m.setup()
|
||||||
|
if len(config) > 0 {
|
||||||
|
err := json.Unmarshal(config, &m.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.init = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SampleCollector) Read(interval time.Duration, out *[]lp.MutableMetric) {
|
||||||
|
if !m.init {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// tags for the metric, if type != node use proper type and type-id
|
||||||
|
tags := map[string][string]{"type" : "node"}
|
||||||
|
// Each metric has exactly one field: value !
|
||||||
|
value := map[string]interface{}{"value": int(x)}
|
||||||
|
y, err := lp.New("sample_metric", tags, value, time.Now())
|
||||||
|
if err == nil {
|
||||||
|
*out = append(*out, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SampleCollector) Close() {
|
||||||
|
m.init = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user