cl 104 parse
This commit is contained in:
parent
c76eb57f12
commit
f39c912e9a
|
|
@ -9,4 +9,4 @@ steps:
|
|||
GO111MODULE: on
|
||||
GOPROXY: https://goproxy.cn,direct
|
||||
commands:
|
||||
- go build -tags "custom,inputs.cl_kafka_consumer,outputs.influxdb_v2,parsers.phasor_binary" ./cmd/telegraf
|
||||
- go build -tags "custom,outputs.influxdb_v2" ./cmd/telegraf
|
||||
|
|
@ -27,6 +27,7 @@ Protocol, JSON format, or Apache Avro format.
|
|||
- [Wavefront](/plugins/parsers/wavefront)
|
||||
- [XPath](/plugins/parsers/xpath) (supports XML, JSON, MessagePack, Protocol Buffers)
|
||||
- [PhasorBinary](/plugins/parsers/phasor_binary) (supports special binary from CL)
|
||||
- [CL104](/plugins/parsers/cl_104) (supports special 104 from CL)
|
||||
|
||||
Any input plugin containing the `data_format` option can use it to select the
|
||||
desired parser:
|
||||
|
|
|
|||
|
|
@ -37,3 +37,12 @@
|
|||
compression_codec = 4
|
||||
max_message_len = 1000000
|
||||
data_format = "phasor_binary"
|
||||
|
||||
[[inputs.cl_104]]
|
||||
service_address = "tcp://:8899"
|
||||
path_cl = "/api/104"
|
||||
path_up = "/api/104up"
|
||||
pong_wait = "60s"
|
||||
ping_period = "54s"
|
||||
write_waite = "10s"
|
||||
data_format = "cl_104"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
//go:build custom || inputs || inputs.cl_104
|
||||
|
||||
package all
|
||||
|
||||
import _ "github.com/influxdata/telegraf/plugins/inputs/cl_104" // register plugin
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !custom || inputs || inputs.cl_kafka_consumer
|
||||
//go:build custom || inputs || inputs.cl_kafka_consumer
|
||||
|
||||
package all
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# CL 104 Input Plugin
|
||||
|
||||
This plugin listens for metrics sent via WS in any of the supported
|
||||
[data formats][data_formats].
|
||||
|
||||
⭐ Telegraf v1.9.0
|
||||
🏷️ server
|
||||
💻 all
|
||||
|
||||
[data_formats]: /docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
## Service Input <!-- @/docs/includes/service_input.md -->
|
||||
|
||||
This plugin is a service input. Normal plugins gather metrics determined by the
|
||||
interval setting. Service plugins start a service to listens and waits for
|
||||
metrics or events to occur. Service plugins have two key differences from
|
||||
normal plugins:
|
||||
|
||||
1. The global or plugin specific `interval` setting may not apply
|
||||
2. The CLI options of `--test`, `--test-wait`, and `--once` may not produce
|
||||
output for this plugin
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Generic HTTP write listener
|
||||
[[inputs.cl_104]]
|
||||
## Address to host HTTP listener on
|
||||
## can be prefixed by protocol tcp, or unix if not provided defaults to tcp
|
||||
## if unix network type provided it should be followed by absolute path for unix socket
|
||||
service_address = "tcp://:8080"
|
||||
## service_address = "tcp://:8443"
|
||||
|
||||
## Paths to listen to.
|
||||
# path_cl="/api/104"
|
||||
# path_up="/api/104up"
|
||||
|
||||
## maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
## pong wait
|
||||
# pong_wait="60s"
|
||||
## ping period
|
||||
# ping_period="54s"
|
||||
## write_wait
|
||||
# write_wait="10s"
|
||||
|
||||
## Set one or more allowed client CA certificate file names to
|
||||
## enable mutually authenticated TLS connections
|
||||
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
|
||||
|
||||
## Add service certificate and key
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
|
||||
## Minimal TLS version accepted by the server
|
||||
# tls_min_version = "TLS12"
|
||||
|
||||
## Optional username and password to accept for HTTP basic authentication.
|
||||
## You probably want to make sure you have TLS configured above for this.
|
||||
# basic_username = "foobar"
|
||||
# basic_password = "barfoo"
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "cl_104"
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
Metrics are collected from the part of the request specified by the
|
||||
`data_source` param and are parsed depending on the value of `data_format`.
|
||||
|
||||
## Example Output
|
||||
|
||||
## Troubleshooting
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package cl_104
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
var once sync.Once
|
||||
|
||||
type CL104 struct {
|
||||
ServiceAddress string `toml:"service_address"`
|
||||
ReadTimeout config.Duration `toml:"read_timeout"`
|
||||
WriteTimeout config.Duration `toml:"write_timeout"`
|
||||
PathCl string `toml:"path_cl"`
|
||||
PathUp string `toml:"path_up"`
|
||||
PongWait config.Duration `toml:"pong_wait"`
|
||||
PingPeriod config.Duration `toml:"ping_period"`
|
||||
WriteWait config.Duration `toml:"write_wait"`
|
||||
BasicUsername string `toml:"basic_username"`
|
||||
BasicPassword string `toml:"basic_password"`
|
||||
|
||||
common_tls.ServerConfig
|
||||
tlsConf *tls.Config
|
||||
|
||||
timeFunc
|
||||
Log telegraf.Logger
|
||||
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
|
||||
listener net.Listener
|
||||
url *url.URL
|
||||
|
||||
route map[string]func(res http.ResponseWriter, req *http.Request)
|
||||
upChan chan []byte // confirm
|
||||
clChan chan []byte // command
|
||||
|
||||
telegraf.Parser
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
|
||||
type msg struct {
|
||||
TI int `json:"ti"`
|
||||
COT int `json:"cot"`
|
||||
PN int `json:"pn"`
|
||||
CA int `json:"ca"`
|
||||
Infos []any `json:"infos"`
|
||||
}
|
||||
|
||||
type wsMsg struct {
|
||||
mt int
|
||||
data []byte
|
||||
}
|
||||
|
||||
type wsSession struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
ctrlCh chan wsMsg
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
WriteBufferPool: &sync.Pool{
|
||||
New: func() any {
|
||||
return make([]byte, 1024)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// timeFunc provides a timestamp for the metrics
|
||||
type timeFunc func() time.Time
|
||||
|
||||
func (*CL104) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (h *CL104) Init() error {
|
||||
tlsConf, err := h.ServerConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protoRegex := regexp.MustCompile(`\w://`)
|
||||
if !protoRegex.MatchString(h.ServiceAddress) {
|
||||
h.ServiceAddress = "tcp://" + h.ServiceAddress
|
||||
}
|
||||
|
||||
u, err := url.Parse(h.ServiceAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing address failed: %w", err)
|
||||
}
|
||||
|
||||
h.url = u
|
||||
h.tlsConf = tlsConf
|
||||
|
||||
h.route = map[string]func(res http.ResponseWriter, req *http.Request){
|
||||
h.PathCl: h.serveClstream,
|
||||
h.PathUp: h.serveUpstream,
|
||||
}
|
||||
h.upChan = make(chan []byte, 16)
|
||||
h.clChan = make(chan []byte, 16)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *CL104) SetParser(parser telegraf.Parser) {
|
||||
h.Parser = parser
|
||||
}
|
||||
|
||||
func (h *CL104) Start(acc telegraf.Accumulator) error {
|
||||
|
||||
var listener net.Listener
|
||||
var err error
|
||||
if h.tlsConf != nil {
|
||||
listener, err = tls.Listen(h.url.Scheme, h.url.Host, h.tlsConf)
|
||||
} else {
|
||||
listener, err = net.Listen(h.url.Scheme, h.url.Host)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.listener = listener
|
||||
|
||||
if h.ReadTimeout < config.Duration(time.Second) {
|
||||
h.ReadTimeout = config.Duration(time.Second * 10)
|
||||
}
|
||||
if h.WriteTimeout < config.Duration(time.Second) {
|
||||
h.WriteTimeout = config.Duration(time.Second * 10)
|
||||
}
|
||||
if h.PongWait <= config.Duration(time.Second) {
|
||||
h.PongWait = config.Duration(time.Second * 60)
|
||||
}
|
||||
if h.PingPeriod <= config.Duration(time.Second) {
|
||||
h.PingPeriod = (h.PongWait * 9) / 10
|
||||
}
|
||||
if h.WriteWait <= config.Duration(time.Second) {
|
||||
h.PongWait = config.Duration(time.Second * 10)
|
||||
}
|
||||
|
||||
h.acc = acc
|
||||
|
||||
server := h.createHTTPServer()
|
||||
|
||||
h.wg.Add(1)
|
||||
go func() {
|
||||
defer h.wg.Done()
|
||||
if err := server.Serve(h.listener); err != nil {
|
||||
if !errors.Is(err, net.ErrClosed) {
|
||||
h.Log.Errorf("Serve failed: %v", err)
|
||||
}
|
||||
close(h.close)
|
||||
}
|
||||
}()
|
||||
|
||||
h.Log.Infof("Listening on %s", h.listener.Addr().String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*CL104) Gather(telegraf.Accumulator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *CL104) Stop() {
|
||||
if h.listener != nil {
|
||||
h.listener.Close()
|
||||
}
|
||||
h.wg.Wait()
|
||||
}
|
||||
|
||||
// ServeHTTP implements [http.Handler]
|
||||
func (h *CL104) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
|
||||
handler, ok := h.route[req.URL.Path]
|
||||
if !ok {
|
||||
handler = http.NotFound
|
||||
}
|
||||
|
||||
h.authenticateIfSet(handler, res, req)
|
||||
}
|
||||
|
||||
func (h *CL104) createHTTPServer() *http.Server {
|
||||
return &http.Server{
|
||||
Addr: h.ServiceAddress,
|
||||
Handler: h,
|
||||
ReadTimeout: time.Duration(h.ReadTimeout),
|
||||
WriteTimeout: time.Duration(h.WriteTimeout),
|
||||
TLSConfig: h.tlsConf,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) authenticateIfSet(handler http.HandlerFunc, res http.ResponseWriter, req *http.Request) {
|
||||
if h.BasicUsername != "" && h.BasicPassword != "" {
|
||||
reqUsername, reqPassword, ok := req.BasicAuth()
|
||||
if !ok ||
|
||||
subtle.ConstantTimeCompare([]byte(reqUsername), []byte(h.BasicUsername)) != 1 ||
|
||||
subtle.ConstantTimeCompare([]byte(reqPassword), []byte(h.BasicPassword)) != 1 {
|
||||
http.Error(res, "Unauthorized.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handler(res, req)
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("cl_104", func() telegraf.Input {
|
||||
return &CL104{
|
||||
timeFunc: time.Now,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (h *CL104) writeControl(session *wsSession, mt int, payload []byte) error {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return session.ctx.Err()
|
||||
case session.ctrlCh <- wsMsg{mt: mt, data: payload}:
|
||||
return nil
|
||||
case <-time.After(time.Duration(h.WriteWait)):
|
||||
return errors.New("write control timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) setUpSessionConn(session *wsSession) error {
|
||||
session.conn.SetPongHandler(func(appData string) error {
|
||||
session.conn.SetReadDeadline(time.Now().Add(time.Duration(h.PongWait)))
|
||||
return nil
|
||||
})
|
||||
|
||||
session.conn.SetPingHandler(func(appData string) error {
|
||||
if err := h.writeControl(session, websocket.PongMessage, []byte(appData)); err != nil {
|
||||
session.cancel()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
session.conn.SetCloseHandler(func(code int, text string) error {
|
||||
session.cancel()
|
||||
return nil
|
||||
})
|
||||
|
||||
return session.conn.SetReadDeadline(time.Now().Add(time.Duration(h.PongWait)))
|
||||
}
|
||||
|
||||
func (h *CL104) sendPing(session *wsSession) {
|
||||
|
||||
ticker := time.NewTicker(time.Duration(h.PingPeriod))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := h.writeControl(session, websocket.PingMessage, nil)
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tooLarge(res http.ResponseWriter) error {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
_, err := res.Write([]byte(`{"code":1,"msg":"http: request body too large"}`))
|
||||
return err
|
||||
}
|
||||
|
||||
func methodNotAllowed(res http.ResponseWriter) error {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, err := res.Write([]byte(`{"code":1,"msg":"http: method not allowed"}`))
|
||||
return err
|
||||
}
|
||||
|
||||
func badRequest(res http.ResponseWriter) error {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
_, err := res.Write([]byte(`{"code":1,"msg":"http: bad request"}`))
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package cl_104
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
testMsg = `{"ti":30,"cot":3,"ca":1,"infos":[{"ioa":121,"val":1,"q":0,"ms":1768205453000}]}`
|
||||
badMsg = "blahblahblah: 42\n"
|
||||
emptyMsg = ""
|
||||
|
||||
basicUsername = "test-username-please-ignore"
|
||||
basicPassword = "super-secure-password!"
|
||||
)
|
||||
|
||||
func newTestCL104() (*CL104, error) {
|
||||
parser := &influx.Parser{}
|
||||
if err := parser.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listener := &CL104{
|
||||
Log: testutil.Logger{},
|
||||
ServiceAddress: "localhost:0",
|
||||
Parser: parser,
|
||||
timeFunc: time.Now,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
return listener, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package cl_104
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
)
|
||||
|
||||
var clConnNum int64
|
||||
|
||||
func (h *CL104) serveClstream(res http.ResponseWriter, req *http.Request) {
|
||||
select {
|
||||
case <-h.close:
|
||||
res.WriteHeader(http.StatusGone)
|
||||
return
|
||||
default:
|
||||
if atomic.SwapInt64(&clConnNum, 1) > 0 {
|
||||
res.WriteHeader(http.StatusConflict)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(res, req, nil)
|
||||
if err != nil {
|
||||
h.Log.Error(err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
stopCtx, stopCancel := context.WithCancel(context.Background())
|
||||
defer stopCancel()
|
||||
|
||||
session := &wsSession{
|
||||
conn: conn,
|
||||
ctx: stopCtx,
|
||||
cancel: stopCancel,
|
||||
ctrlCh: make(chan wsMsg, 2),
|
||||
}
|
||||
|
||||
if err := h.setUpSessionConn(session); err != nil {
|
||||
h.Log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.startClWorkers(session)
|
||||
}
|
||||
|
||||
func (h *CL104) startClWorkers(session *wsSession) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.monitorClWrite(session)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.monitorClRead(session)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.sendPing(session)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
atomic.SwapInt64(&clConnNum, 0)
|
||||
}
|
||||
|
||||
func (h *CL104) monitorClWrite(session *wsSession) {
|
||||
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return
|
||||
case ctrl := <-session.ctrlCh:
|
||||
err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
err = session.conn.WriteControl(ctrl.mt, ctrl.data, time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
case msg := <-h.clChan:
|
||||
err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
err = session.conn.WriteMessage(websocket.TextMessage, msg)
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) monitorClRead(session *wsSession) {
|
||||
for {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return
|
||||
default:
|
||||
mt, rm, err := session.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
if ce, ok := err.(*websocket.CloseError); ok {
|
||||
h.Log.Infof("client closed with code:", ce.Code, "text:", ce.Text)
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
h.Log.Error("server read:", err)
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
switch mt {
|
||||
case websocket.TextMessage:
|
||||
if err := h.fromClstream(session, rm); err != nil {
|
||||
h.Log.Error(err)
|
||||
}
|
||||
default:
|
||||
h.Log.Info("not text:", string(rm))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) fromClstream(session *wsSession, m []byte) error {
|
||||
msg := new(msg)
|
||||
if err := json.Unmarshal(m, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case msg.TI >= 1 && msg.TI <= 40, msg.TI >= 110 && msg.TI <= 119:
|
||||
metrics, err := h.Parse(m)
|
||||
if err != nil {
|
||||
h.Log.Debugf("parse error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if len(metrics) == 0 {
|
||||
once.Do(func() {
|
||||
h.Log.Debug(internal.NoMetricsCreatedMsg)
|
||||
})
|
||||
}
|
||||
|
||||
for _, m := range metrics {
|
||||
h.acc.AddMetric(m)
|
||||
}
|
||||
|
||||
case msg.TI >= 45 && msg.TI <= 69, msg.TI >= 100 && msg.TI <= 109:
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return session.ctx.Err()
|
||||
default:
|
||||
msg.PN = msg.COT & 0x40
|
||||
msg.COT = msg.COT & 0x3F
|
||||
if m, err := json.Marshal(msg); err != nil {
|
||||
return err
|
||||
} else {
|
||||
select {
|
||||
case h.upChan <- m:
|
||||
case <-time.After(time.Second * 5):
|
||||
h.Log.Error("drop up msg:", msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid TI: %d", msg.TI)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Generic HTTP write listener
|
||||
[[inputs.cl_104]]
|
||||
## Address to host HTTP listener on
|
||||
## can be prefixed by protocol tcp, or unix if not provided defaults to tcp
|
||||
## if unix network type provided it should be followed by absolute path for unix socket
|
||||
service_address = "tcp://:8080"
|
||||
## service_address = "tcp://:8443"
|
||||
|
||||
## Paths to listen to.
|
||||
# path_cl="/api/104"
|
||||
# path_up="/api/104up"
|
||||
|
||||
## maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
## pong wait
|
||||
# pong_wait="60s"
|
||||
## ping period
|
||||
# ping_period="54s"
|
||||
## write_wait
|
||||
# write_wait="10s"
|
||||
|
||||
## Set one or more allowed client CA certificate file names to
|
||||
## enable mutually authenticated TLS connections
|
||||
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
|
||||
|
||||
## Add service certificate and key
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
|
||||
## Minimal TLS version accepted by the server
|
||||
# tls_min_version = "TLS12"
|
||||
|
||||
## Optional username and password to accept for HTTP basic authentication.
|
||||
## You probably want to make sure you have TLS configured above for this.
|
||||
# basic_username = "foobar"
|
||||
# basic_password = "barfoo"
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "cl_104"
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package cl_104
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upConnNum int64
|
||||
|
||||
func (h *CL104) serveUpstream(res http.ResponseWriter, req *http.Request) {
|
||||
select {
|
||||
case <-h.close:
|
||||
res.WriteHeader(http.StatusGone)
|
||||
return
|
||||
default:
|
||||
if atomic.SwapInt64(&upConnNum, 1) > 0 {
|
||||
res.WriteHeader(http.StatusConflict)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(res, req, nil)
|
||||
if err != nil {
|
||||
h.Log.Error(err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
stopCtx, stopCancel := context.WithCancel(context.Background())
|
||||
defer stopCancel()
|
||||
|
||||
session := &wsSession{
|
||||
conn: conn,
|
||||
ctx: stopCtx,
|
||||
cancel: stopCancel,
|
||||
ctrlCh: make(chan wsMsg, 2),
|
||||
}
|
||||
|
||||
if err := h.setUpSessionConn(session); err != nil {
|
||||
h.Log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.startUpWorkers(session)
|
||||
}
|
||||
|
||||
func (h *CL104) startUpWorkers(session *wsSession) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.monitorUpWrite(session)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.monitorUpRead(session)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
h.sendPing(session)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
atomic.SwapInt64(&upConnNum, 0)
|
||||
}
|
||||
|
||||
func (h *CL104) monitorUpWrite(session *wsSession) {
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return
|
||||
case ctrl := <-session.ctrlCh:
|
||||
err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
err = session.conn.WriteControl(ctrl.mt, ctrl.data, time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
case msg := <-h.upChan:
|
||||
err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
err = session.conn.WriteMessage(websocket.TextMessage, msg)
|
||||
if err != nil {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) monitorUpRead(session *wsSession) {
|
||||
for {
|
||||
select {
|
||||
case <-session.ctx.Done():
|
||||
return
|
||||
default:
|
||||
mt, rm, err := session.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
if ce, ok := err.(*websocket.CloseError); ok {
|
||||
h.Log.Infof("client closed with code:", ce.Code, "text:", ce.Text)
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
h.Log.Error("server read:", err)
|
||||
session.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
switch mt {
|
||||
case websocket.TextMessage:
|
||||
if err := h.fromUpstream(rm); err != nil {
|
||||
h.Log.Error(err)
|
||||
}
|
||||
default:
|
||||
h.Log.Info("not text:", string(rm))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CL104) fromUpstream(m []byte) error {
|
||||
msg := new(msg)
|
||||
if err := json.Unmarshal(m, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.TI >= 45 && msg.TI <= 69 || msg.TI >= 100 && msg.TI <= 109 {
|
||||
select {
|
||||
case h.clChan <- m:
|
||||
case <-time.After(time.Second * 5):
|
||||
h.Log.Error("drop cl msg:", msg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
//go:build custom || parsers || parsers.cl_104
|
||||
|
||||
package all
|
||||
|
||||
import _ "github.com/influxdata/telegraf/plugins/parsers/cl_104" // register plugin
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !custom || parsers || parsers.phasor_binary
|
||||
//go:build custom || parsers || parsers.phasor_binary
|
||||
|
||||
package all
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# CL 104 Parser Plugin
|
||||
|
||||
This parser takes valid JSON input and turns it into line protocol.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[[inputs.cl_104]]
|
||||
## Address to host HTTP listener on
|
||||
## can be prefixed by protocol tcp, or unix if not provided defaults to tcp
|
||||
## if unix network type provided it should be followed by absolute path for unix socket
|
||||
service_address = "tcp://:8080"
|
||||
## service_address = "tcp://:8443"
|
||||
|
||||
## Paths to listen to.
|
||||
# path_cl="/api/104"
|
||||
# path_up="/api/104up"
|
||||
|
||||
## maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
## pong wait
|
||||
# pong_wait="60s"
|
||||
## ping period
|
||||
# ping_period="54s"
|
||||
## write_wait
|
||||
# write_wait="10s"
|
||||
|
||||
## Set one or more allowed client CA certificate file names to
|
||||
## enable mutually authenticated TLS connections
|
||||
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
|
||||
|
||||
## Add service certificate and key
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
|
||||
## Minimal TLS version accepted by the server
|
||||
# tls_min_version = "TLS12"
|
||||
|
||||
## Optional username and password to accept for HTTP basic authentication.
|
||||
## You probably want to make sure you have TLS configured above for this.
|
||||
# basic_username = "foobar"
|
||||
# basic_password = "barfoo"
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "cl_104"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package cl_104
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/plugins/parsers"
|
||||
)
|
||||
|
||||
// Parser
|
||||
type Parser struct {
|
||||
DefaultMetricName string
|
||||
DefaultTags map[string]string
|
||||
Log telegraf.Logger
|
||||
|
||||
// **** The struct fields below this comment are used for processing individual configs ****
|
||||
|
||||
// measurementName is the name of the current config used in each line protocol
|
||||
measurementName string
|
||||
|
||||
// parseMutex is here because Parse() is not threadsafe. If it is made threadsafe at some point, then we won't need it anymore.
|
||||
parseMutex sync.Mutex
|
||||
}
|
||||
|
||||
type info struct {
|
||||
IOA int `json:"ioa"`
|
||||
Val float64 `json:"val"`
|
||||
Q int `json:"q"`
|
||||
MS int64 `json:"ms"`
|
||||
}
|
||||
|
||||
type msg struct {
|
||||
TI int `json:"ti"`
|
||||
COT int `json:"cot"`
|
||||
PN int `json:"pn"`
|
||||
CA int `json:"ca"`
|
||||
Infos []*info `json:"infos"`
|
||||
}
|
||||
|
||||
func (p *Parser) Init() error {
|
||||
if len(p.measurementName) == 0 {
|
||||
p.measurementName = "cl104"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(input []byte) ([]telegraf.Metric, error) {
|
||||
msg := new(msg)
|
||||
if err := json.Unmarshal(input, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metrics := make([]telegraf.Metric, 0, len(msg.Infos))
|
||||
for _, info := range msg.Infos {
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"ca": strconv.Itoa(msg.CA),
|
||||
"cot": strconv.Itoa(msg.COT),
|
||||
"ioa": strconv.Itoa(info.IOA),
|
||||
"ti": strconv.Itoa(msg.TI),
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"val": info.Val,
|
||||
}
|
||||
|
||||
tm := time.Now()
|
||||
if info.MS > 0 {
|
||||
tm = time.UnixMilli(info.MS)
|
||||
}
|
||||
|
||||
metrics = append(metrics, metric.New(
|
||||
p.measurementName,
|
||||
tags,
|
||||
fields,
|
||||
tm,
|
||||
))
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (*Parser) ParseLine(string) (telegraf.Metric, error) {
|
||||
return nil, errors.New("parsing line is not implemented")
|
||||
}
|
||||
|
||||
func (p *Parser) SetDefaultTags(tags map[string]string) {
|
||||
p.DefaultTags = tags
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register all variants
|
||||
parsers.Add("cl_104",
|
||||
func(defaultMetricName string) telegraf.Parser {
|
||||
return &Parser{
|
||||
DefaultMetricName: defaultMetricName,
|
||||
measurementName: "cl104",
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package cl_104
|
||||
Loading…
Reference in New Issue