Why procduces my two identical systemctl timers different amount of outputs? - systemd

I have two raspberry pi with almost identical setup.
On both I have a systemctl service and a timer which run every 15 seconds.
The job calls a go script, which reads out the system temperature and creates a log entry in my mariadb database. The database is running on one of the raspberries. Therefore the only difference in the two go scripts are the hostname of the database and an identifier variable so I can see in the database from which the database entry is coming from. The rest is identical.
The problem is that raspberry1 produces more database entries than raspberry2.
When I start with a fresh database with zero entries. Then 1 have about 120 entries from raspberry1 and about 70 entries from raspberrie two. The error continues. After more time I get for example 1000 entries and 700 entries.
Does anyone know where the differences could coming from?
Maybe when both jobs want to create a new entry in the mariadb at the same time?
But I'm almost sure that this is handled somehow by mariadb.
Here are the details:
FanLog.service
[Unit]
Description=Lüfter Temperatur Logger Service
After=network.target
[Service]
ExecStart=/root/go/temperatur_logger
WorkingDirectory=/root/go
StandardOutput=append:/var/log/TempLogger.log
StandardError=append:/var/log/TempLogger_error.log
User=root
FanLog.timer
[Unit]
Description=Fanlogger Timer
[Timer]
# OnBootSec=15min
# Wochentage-dd-mm hh:mm:ss
# OnCalendar=*-*-* *:05:00
# OnUnitActiveSec=1min
# OnCalendar=minutely
OnCalendar=*-*-* *:*:0/15
# OnCalendar=*-*-* *:0/0:20
[Install]
WantedBy=timers.target
The output of "systemctl status FanLog.timer" seems to be okay.
I can see at maximum the 15 seconds down to a few ms and then again 15 seconds if I repeat.
⚡ root#DeskPi  /etc/systemd/system  systemctl status FanLog.timer
● FanLog.timer - Fanlogger Timer
Loaded: loaded (/etc/systemd/system/FanLog.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Mon 2021-09-20 06:07:22 CEST; 39min ago
Trigger: Mon 2021-09-20 06:46:45 CEST; 13s left
Sep 20 06:07:22 DeskPi systemd[1]: Stopping Fanlogger Timer.
Sep 20 06:07:22 DeskPi systemd[1]: Started Fanlogger Timer.
temperatur_logger.go
package main
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"os/exec"
"time"
"github.com/stianeikeland/go-rpio"
_ "github.com/go-sql-driver/mysql"
)
const (
username = "TempLogger"
password = "XXXXXXXXXXXXX"
hostname = "192.168.1.20:3306" // Different on other device
dbname = "TempLog"
table = "TempLog"
DeviceName = "OctoPi" // Different on other device
)
var (
// Use mcu pin 24, corresponds to GPIO3 on the pi
pin = rpio.Pin(24)
)
func add(x int, y int) int {
return x + y
}
func get_temp(temperatur string) string {
var outputlaenge int
var aktuelle_temperatur string
// Befehl
cmd := exec.Command("sudo", "/usr/bin/vcgencmd", "measure_temp")
// run command
if output, err := cmd.Output(); err != nil {
fmt.Println("Error", err)
} else {
outputlaenge = len(output)
aktuelle_temperatur = string(output[outputlaenge-7 : outputlaenge-3])
// fmt.Println(string(output[outputlaenge-7 : outputlaenge-3]))
}
return aktuelle_temperatur
}
func dsn(dbName string) string {
return fmt.Sprintf("%s:%s#tcp(%s)/%s", username, password, hostname, dbName)
}
func dbConnection() (*sql.DB, error) {
db, err := sql.Open("mysql", dsn(""))
if err != nil {
log.Printf("Error %s when opening DB\n", err)
return nil, err
}
defer db.Close()
ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
if err != nil {
log.Printf("Error %s when creating DB\n", err)
return nil, err
}
db.Close()
db, err = sql.Open("mysql", dsn(dbname))
if err != nil {
log.Printf("Error %s when opening DB", err)
return nil, err
}
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(20)
db.SetConnMaxLifetime(time.Minute * 5)
ctx, cancelfunc = context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
err = db.PingContext(ctx)
if err != nil {
log.Printf("Errors %s pinging DB", err)
return nil, err
}
log.Printf("Connected to DB %s successfully\n", dbname)
return db, nil
}
type temperatur_eintrag struct {
DeviceName string
record_date string
temperature string
fan bool
}
func insert(db *sql.DB, p temperatur_eintrag) error {
query := "INSERT INTO " + table + "(device_name, record_date, temperature, fan) VALUES (?, ?, ?, ?)"
ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
stmt, err := db.PrepareContext(ctx, query)
if err != nil {
log.Printf("Error %s when preparing SQL statement", err)
return err
}
defer stmt.Close()
res, err := stmt.ExecContext(ctx, p.DeviceName, p.record_date, p.temperature, p.fan)
if err != nil {
log.Printf("Error %s when inserting row into products table", err)
return err
}
rows, err := res.RowsAffected()
if err != nil {
log.Printf("Error %s when finding rows affected", err)
return err
}
log.Printf("%d log entry created ", rows)
return nil
}
func main() {
currentTime := time.Now()
var tempi string
var myFan bool
db, err := dbConnection()
if err != nil {
log.Printf("Error %s when getting db connection", err)
return
}
defer db.Close()
log.Printf("Successfully connected to database")
// Open and map memory to access gpio, check for errors
if err := rpio.Open(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// Unmap gpio memory when done
defer rpio.Close()
// pin.PullUp()
if pin.Read() == 1 {
fmt.Println("Pin High")
myFan = true
} else {
fmt.Println("Pin Low")
myFan = false
}
p := temperatur_eintrag{
DeviceName: DeviceName,
record_date: currentTime.Format("2006-01-02 15:04:05"),
temperature: get_temp(tempi),
fan: myFan,
}
err = insert(db, p)
if err != nil {
log.Printf("Insert failed with error %s", err)
return
}
}

I have found the solution:
One of these two options made it work:
Persistent=true
AccuracySec=2sec
Not sure which one exactly as I didn't have tested more.
FanLog.timer looks now so:
[Unit]
Description=Fanlogger Timer
[Timer]
OnCalendar=*:*:0/10
Persistent=true
AccuracySec=2sec
[Install]
WantedBy=timers.target

Related

rows.Next() halts after some number of rows

Im newbie in Golang, so it may be simple for professionals but I got stuck with no idea what to do next.
I'm making some migration app that extract some data from oracle DB and after some conversion insert it to Postges one-by-one.
The result of native Query in DB console returns about 400k of rows and takes about 13 sec to end.
The data from Oracle extracts with rows.Next() with some strange behavior:
First 25 rows extracted fast enough, then about few sec paused, then new 25 rows until it pauses "forever".
Here is the function:
func GetHrTicketsFromOra() (*sql.Rows, error) {
rows, err := oraDB.Query("select id,STATE_ID,REMEDY_ID,HEADER,CREATE_DATE,TEXT,SOLUTION,SOLUTION_USER_LOGIN,LAST_SOLUTION_DATE from TICKET where SOLUTION_GROUP_ID = 5549")
if err != nil {
println("Error while getting rows from Ora")
return nil, err
}
log.Println("Finished legacy tickets export")
return rows, err
}
And here I export data:
func ConvertRows(rows *sql.Rows, c chan util.ArchTicket, m chan int) error {
log.Println("Conversion start")
defer func(rows *sql.Rows) {
err := rows.Close()
if err != nil {
log.Println("ORA connection closed", err)
return
}
}(rows)
for rows.Next() {
log.Println("Reading the ticket")
ot := util.OraTicket{}
at := util.ArchTicket{}
err := rows.Scan(&ot.ID, &ot.StateId, &ot.RemedyId, &ot.Header, &ot.CreateDate, &ot.Text, &ot.Solution, &ot.SolutionUserLogin, &ot.LastSolutionDate)
if err != nil {
log.Println("Error while reading row", err)
return err
}
at = convertLegTOArch(ot)
c <- at
}
if err := rows.Err(); err != nil {
log.Println("Error while reading row", err)
return err
}
m <- 1
return nil
}
UPD. I use "github.com/sijms/go-ora/v2" driver
UPD2. Seems like the root cause of the problem is in TEXT and SOLUTION fields of the result rows. They are varchar and can be big enough. Deleting them from the direct query changes the time of execution from 13sec to 258ms. But I still have no idea what to do with that.
UPD3.
Minimal reproducible example
package main
import (
"database/sql"
_ "github.com/sijms/go-ora/v2"
"log"
)
var oraDB *sql.DB
var con = "oracle://login:password#ora_db:1521/database"
func InitOraDB(dataSourceName string) error {
var err error
oraDB, err = sql.Open("oracle", dataSourceName)
if err != nil {
return err
}
return oraDB.Ping()
}
func GetHrTicketsFromOra() {
var ot string
rows, err := oraDB.Query("select TEXT from TICKET where SOLUTION_GROUP_ID = 5549")
if err != nil {
println("Error while getting rows from Ora")
}
for rows.Next() {
log.Println("Reading the ticket")
err := rows.Scan(&ot)
if err != nil {
log.Println("Reading failed", err)
}
log.Println("Read:")
}
log.Println("Finished legacy tickets export")
}
func main() {
err := InitOraDB(con)
if err != nil {
log.Println("Error connection Ora")
}
GetHrTicketsFromOra()
}

redis pool.Get() takes a longer time to return connection in golang

This is the code I have written to get to create redisPool at compile time.
var RedisPool *redis.Pool //set at compile time
func RedisSetup() {
RedisPool = newRedisPool(serverUrl, password)
c := RedisPool.Get()
defer c.Close()
pong, err := redis.String(c.Do("PING"))
if err != nil{
fmt.Println("error while connecting redis ", err)
}
fmt.Println("Redis Ping:", pong)
}
func newRedisPool(server, password string) *redis.Pool {
pool := &redis.Pool{
MaxIdle: 10,
MaxActive: 100,
IdleTimeout: 5 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", server, redis.DialUseTLS(true), redis.DialTLSSkipVerify(true))
if err != nil {
log.Printf("ERROR: fail initializing the redis pool: %s", err.Error())
fmt.Println("server conn err", err)
return nil, err
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
fmt.Println("auth err", err)
return nil, err
}
}
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
return pool
}
Now, this is the function i am using for doing redis operations like set or get.
func GetCacheValue(ctx context.Context, key string) (string, error) {
startTime := time.Now()
span, ctx := apm.StartSpan(ctx, "get cache " + key, "request")
defer span.End()
conn := apmredigo.Wrap(getPool(ctx)).WithContext(ctx)
val, err := redis.String(conn.Do("GET", key))
if err != nil {
return "", err
}
redisPool.Close()
fmt.Println("\n redis get time : ", time.Since(startTime), "\n")
return val, nil
}
func getPool(ctx context.Context) redis.Conn {
startTime :=time.Now()
span, ctx := apm.StartSpan(ctx, "get connection ", "request")
defer span.End()
conn := RedisPool.Get()
fmt.Println("\nPool get time : ", time.Since(startTime), "\n")
return conn
}
This is the output i am getting, I have hit 3 request and got different different response time ,sometime it respond within 1ms or 2ms and sometime it takes around 100ms. Why so much difference? Can someone please help, is there anything I am missing?
console output 1 console output 2 apm server output

"context deadline exceeded" AFTER first client-to-server request & response

In a simple gRPC example, I connect to the server, request to send a text message, and in return a success message is sent. Once the server is live, the first client request is successful, with no errors.
However, after the first try, each subsequent request (identical to the first) return this error, and does not return a response as the results (the text message is still sent, but the generated ID is not sent back):
rpc error: code = DeadlineExceeded desc = context deadline exceeded
After debugging a little bit, I found that the error
func (c *messageSchedulerClient) SendText(ctx context.Context, in *TextRequest, opts ...grpc.CallOption) (*MessageReply, error) {
...
err := c.cc.Invoke(ctx, "/communication.MessageScheduler/SendText", in, out, opts...)
...
return nil, err
}
returns
rpc error: code = DeadlineExceeded desc = context deadline exceeded
Here is my client code:
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
c := pb.NewMessageSchedulerClient(conn)
var r *pb.MessageReply
r, err = pbSendText(c, false)
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetId())
err = conn.Close()
if err != nil {
log.Printf("Connection Close Error: %v", err)
}
return
}
func pbSendText(c pb.MessageSchedulerClient, instant bool) (*pb.MessageReply, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
minute := timestamppb.New(time.Now().Add(time.Second * 30))
r, err := c.SendText(ctx, &pb.TextRequest{})
if err != nil {
log.Printf("DEBUG MESSAGE: Error after c.SendText(ctx, in): %v", err)
}
cancel()
return r, err
}
and the server code is...
func (s *MessageServer) SendText(ctx context.Context, in *pb.TextRequest) (*pb.MessageReply, error) {
return &pb.MessageReply{Id: GeneratePublicId()}, nil
}
func GrpcServe() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterMessageSchedulerServer(s, &MessageServer{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
return
}
const Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKMNOPQRSTUVWXYZ0123457"
// executes instantly, O(n)
func GeneratePublicId() string {
return id.NewWithAlphabet(Alphabet)
}
I've tried changing the context to .TODO from .Background. Doesn't work. I am SURE it's something so simple that I'm missing.
Again, the first time I execute the application from the client side, it works. But after the first client application execution, meaning application execution and beyond -- until I restart the gRPC Server, it gives me the error.
The GeneratePublicId function executes almost instantly, as it is O(n) and uses a random number function.

Go GRPC Bidirectional Stream Performance

We are developing a high frequency trading platform and in one of our components we have implemented the grpc with golang. And we needed to use bidirectional streaming in one of our usecases , we made a sample implementation as in below code , however when we test the performance of the code by checking the difference between timestamps of the logs in
Recv Time %v Index: %v Num: %v
Send Time %v, Index: %v, Num: %v
we found out that calling .Send method of the stream from client side and receiving the same data by calling .Recv on the server side tooks approximately 400-800 microseconds which is too low for us. We need maximum 10-50 microseconds performance , and when we read the guidelines we saw that grpc can go up to nanoseconds if both client and server is in the same computer (Which is exactly our case)
So I think we are missing some options or some performance tricks about it. Does anyone know what we can do to increase this performance problem
Client Code:
package main
import (
"context"
"log"
"math/rand"
pb "github.com/pahanini/go-grpc-bidirectional-streaming-example/src/proto"
"time"
"google.golang.org/grpc"
)
func main() {
rand.Seed(time.Now().Unix())
// dail server
conn, err := grpc.Dial(":50005", grpc.WithInsecure())
if err != nil {
log.Fatalf("can not connect with server %v", err)
}
// create stream
client := pb.NewMathClient(conn)
stream, err := client.Max(context.Background())
if err != nil {
log.Fatalf("openn stream error %v", err)
}
var max int32
ctx := stream.Context()
done := make(chan bool)
msgCount := 100
fromMsg := 0
// first goroutine sends random increasing numbers to stream
// and closes int after 10 iterations
go func() {
for i := 1; i <= msgCount; i++ {
// generate random nummber and send it to stream
rnd := int32(i)
req := pb.Request{Num: rnd}
if i-1 >= fromMsg {
sendTime := time.Now().UnixNano()
log.Printf("Send Time %v, Index: %v, Num: %v", sendTime,i-1,req.Num)
}
if err := stream.Send(&req); err != nil {
log.Fatalf("can not send %v", err)
}
//afterSendTime := time.Now().UnixNano()
//log.Printf("After Send Time %v", afterSendTime)
//log.Printf("---------------")
//log.Printf("%d sent", req.Num)
//time.Sleep(time.Millisecond * 200)
}
if err := stream.CloseSend(); err != nil {
log.Println(err)
}
}()
// third goroutine closes done channel
// if context is done
go func() {
<-ctx.Done()
if err := ctx.Err(); err != nil {
log.Println(err)
}
close(done)
}()
<-done
log.Printf("finished with max=%d", max)
}
Server Code:
package main
import (
"io"
"log"
"net"
"time"
pb "github.com/pahanini/go-grpc-bidirectional-streaming-example/src/proto"
"google.golang.org/grpc"
)
type server struct{}
func (s server) Max(srv pb.Math_MaxServer) error {
log.Println("start new server")
var max int32
ctx := srv.Context()
i := 0
fromMsg := 0
for {
// exit if context is done
// or continue
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// receive data from stream
req, err := srv.Recv()
if err == io.EOF {
// return will close stream from server side
log.Println("exit")
return nil
}
if err != nil {
log.Printf("receive error %v", err)
continue
}
if i >= fromMsg {
recvTime := time.Now().UnixNano()
log.Printf("Recv Time %v Index: %v Num: %v", recvTime,i,req.Num)
}
i++
// continue if number reveived from stream
// less than max
if req.Num <= max {
continue
}
// update max and send it to stream
/*
max = req.Num
resp := pb.Response{Result: max}
if err := srv.Send(&resp); err != nil {
log.Printf("send error %v", err)
}
*/
//log.Printf("send new max=%d", max)
}
}
func main() {
// create listiner
lis, err := net.Listen("tcp", ":50005")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// create grpc server
s := grpc.NewServer()
pb.RegisterMathServer(s, server{})
// and start...
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

Golang - Filtering Stdout of Active SSH Session

I'm writing an SSH client in Go that connects to a switch and runs a list of configuration commands. So far, I am able to successfully connect to a switch, run the desired commands, and print the output of the session. The problem comes when the switch is expecting a \n, space, or "q" to be entered when a command's output is too long. For example:
switch#show int status
Port Name Status Vlan Duplex Speed Type
Gi1/0/1 notconnect 100 auto auto 10/100/1000BaseTX
Gi1/0/2 connected 915 a-full a-100 10/100/1000BaseTX
Gi1/0/3 notconnect 100 auto auto 10/100/1000BaseTX
Gi1/0/4 notconnect 100 auto auto 10/100/1000BaseTX
Gi1/0/5 notconnect 230 auto auto 10/100/1000BaseTX
...
Po1 sw-sww-100-sww-0-0 connected trunk a-full 10G
--More-- # Program hangs here; expecting a new line, space, or 'q'
The --More-- prompt is not actually printed to the screen, so simply checking if the current line of Stdout contains --More-- and sending a \n, space, or "q" does not work.
In addition to fixing this problem, I'd like to filter Stdout of the session so that the only thing printed is the output of each command. In other words, I don't want the switch's prompt to be printed to the terminal.
To sum up:
How to capture and print only the output of each command?
How to send a new line, space, or letter "q" when prompted?
Any help is appreciated. Here is my code:
package main
import (
"bufio"
"fmt"
"golang.org/x/crypto/ssh"
"io"
"log"
"os"
"time"
)
type Device struct {
Config *ssh.ClientConfig
Client *ssh.Client
Session *ssh.Session
Stdin io.WriteCloser
Stdout io.Reader
Stderr io.Reader
}
func (d *Device) Connect() error {
client, err := ssh.Dial("tcp", os.Args[1]+":22", d.Config)
if err != nil {
return err
}
session, err := client.NewSession()
if err != nil {
return err
}
sshIn, err := session.StdinPipe()
if err != nil {
return err
}
sshOut, err := session.StdoutPipe()
if err != nil {
return err
}
sshErr, err := session.StderrPipe()
if err != nil {
return err
}
d.Client = client
d.Session = session
d.Stdin = sshIn
d.Stdout = sshOut
d.Stderr = sshErr
return nil
}
func (d *Device) SendCommand(cmd string) error {
if _, err := io.WriteString(d.Stdin, cmd+"\r\n"); err != nil {
return err
}
return nil
}
func (d *Device) SendConfigSet(cmds []string) error {
for _, cmd := range cmds {
if _, err := io.WriteString(d.Stdin, cmd+"\r\n"); err != nil {
return err
}
time.Sleep(time.Second)
}
return nil
}
func (d *Device) PrintOutput() {
r := bufio.NewReader(d.Stdout)
for {
text, err := r.ReadString('\n')
fmt.Printf("%s", text)
if err == io.EOF {
break
}
}
}
func (d *Device) PrintErr() {
r := bufio.NewReader(d.Stderr)
for {
text, err := r.ReadString('\n')
fmt.Printf("%s", text)
if err == io.EOF {
break
}
}
}
func main() {
sshConf := ssh.Config{}
sshConf.Ciphers = append(sshConf.Ciphers, "aes128-cbc", "3des-cbc", "blowfish-cbc", "arcfour")
config := &ssh.ClientConfig{
Config: sshConf,
User: "mwalto7",
Auth: []ssh.AuthMethod{
ssh.Password("Lion$Tiger$Bear$"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Second * 5,
}
sw := &Device{Config: config}
fmt.Println("Connecting to ", os.Args[1])
if err := sw.Connect(); err != nil {
log.Fatal(err)
}
defer sw.Client.Close()
defer sw.Session.Close()
defer sw.Stdin.Close()
if err := sw.Session.Shell(); err != nil {
log.Fatal(err)
}
commands := []string{"show int status", "exit"}
if err := sw.SendConfigSet(commands); err != nil {
log.Fatal(err)
}
sw.Session.Wait()
sw.PrintOutput()
sw.PrintErr()
}
I fixed this problem by sending the command terminal length 0 just after connecting to the switch, before sending the rest of the commands. This disabled the —More— prompt by setting the switch’s terminal height to 0, allowing the whole output of a command to be displayed.

Resources