// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

//go:build !solaris && !windows
// +build !solaris,!windows

package main

import (
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"

	"github.com/syncthing/syncthing/internal/db"
	"github.com/syncthing/syncthing/lib/build"
	"github.com/syncthing/syncthing/lib/locations"
	"github.com/syncthing/syncthing/lib/osutil"
	"github.com/syncthing/syncthing/lib/protocol"
	"golang.org/x/exp/constraints"
)

func startPerfStats(db db.DB) {
	go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()), db)
}

func savePerfStats(file string, db db.DB) {
	fd, err := os.Create(file)
	if err != nil {
		panic(err)
	}

	var prevTime time.Time
	var curRus, prevRus syscall.Rusage
	var curMem, prevMem runtime.MemStats
	var prevIn, prevOut int64

	t0 := time.Now()
	syscall.Getrusage(syscall.RUSAGE_SELF, &prevRus)
	runtime.ReadMemStats(&prevMem)

	fmt.Fprintf(fd, "TIME_S\tCPU_S\tHEAP_KIB\tRSS_KIB\tNETIN_KBPS\tNETOUT_KBPS\tDBSIZE_KIB\tDBLOCAL\n")

	for t := range time.NewTicker(250 * time.Millisecond).C {
		syscall.Getrusage(syscall.RUSAGE_SELF, &curRus)
		runtime.ReadMemStats(&curMem)
		in, out := protocol.TotalInOut()
		timeDiff := t.Sub(prevTime)

		rss := curRus.Maxrss
		if build.IsDarwin {
			rss /= 1024
		}

		folders, _ := db.ListFolders()
		var dbLocal int
		for _, f := range folders {
			local, _ := db.CountLocal(f, protocol.LocalDeviceID)
			dbLocal += local.Files + local.Directories + local.Symlinks
		}

		fmt.Fprintf(
			fd, "%.03f\t%f\t%d\t%d\t%.0f\t%.0f\t%d\t%d\n",
			t.Sub(t0).Seconds(),
			rate(cpusec(&prevRus), cpusec(&curRus), timeDiff, 1),
			(curMem.Sys-curMem.HeapReleased)/1024,
			rss,
			rate(prevIn, in, timeDiff, 1e3),
			rate(prevOut, out, timeDiff, 1e3),
			osutil.DirSize(locations.Get(locations.Database))/1024,
			dbLocal,
		)

		prevTime = t
		prevRus = curRus
		prevMem = curMem
		prevIn, prevOut = in, out
	}
}

func cpusec(r *syscall.Rusage) float64 {
	return float64(r.Utime.Nano()+r.Stime.Nano()) / float64(time.Second)
}

type number interface {
	constraints.Float | constraints.Integer
}

func rate[T number](prev, cur T, d time.Duration, div float64) float64 {
	diff := cur - prev
	rate := float64(diff) / d.Seconds() / div
	return rate
}
