Marek Siarkowicz 718d5ba2b4 Calculate request success rate to provide signal to performance debugging
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2024-04-11 09:36:17 +02:00

213 lines
5.7 KiB
Go

// Copyright 2022 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package report
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"testing"
"github.com/anishathalye/porcupine"
"go.uber.org/zap"
"go.etcd.io/etcd/tests/v3/robustness/model"
)
type ClientReport struct {
ClientID int
KeyValue []porcupine.Operation
Watch []model.WatchOperation
}
func (r ClientReport) SuccessfulOperations() int {
count := 0
for _, op := range r.KeyValue {
resp := op.Output.(model.MaybeEtcdResponse)
if resp.Error == "" {
count++
}
}
return count
}
func (r ClientReport) WatchEventCount() int {
count := 0
for _, op := range r.Watch {
for _, resp := range op.Responses {
count += len(resp.Events)
}
}
return count
}
func persistClientReports(t *testing.T, lg *zap.Logger, path string, reports []ClientReport) {
sort.Slice(reports, func(i, j int) bool {
return reports[i].ClientID < reports[j].ClientID
})
for _, r := range reports {
clientDir := filepath.Join(path, fmt.Sprintf("client-%d", r.ClientID))
err := os.MkdirAll(clientDir, 0700)
if err != nil {
t.Fatal(err)
}
if len(r.Watch) != 0 {
persistWatchOperations(t, lg, filepath.Join(clientDir, "watch.json"), r.Watch)
}
if len(r.KeyValue) != 0 {
persistKeyValueOperations(t, lg, filepath.Join(clientDir, "operations.json"), r.KeyValue)
}
}
}
func LoadClientReports(path string) ([]ClientReport, error) {
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}
reports := []ClientReport{}
for _, file := range files {
if file.IsDir() && strings.HasPrefix(file.Name(), "client-") {
idString := strings.Replace(file.Name(), "client-", "", 1)
id, err := strconv.Atoi(idString)
if err != nil {
return nil, fmt.Errorf("failed to extract clientID from directory: %q", file.Name())
}
r, err := loadClientReport(filepath.Join(path, file.Name()))
if err != nil {
return nil, err
}
r.ClientID = id
reports = append(reports, r)
}
}
sort.Slice(reports, func(i, j int) bool {
return reports[i].ClientID < reports[j].ClientID
})
return reports, nil
}
func loadClientReport(path string) (report ClientReport, err error) {
report.Watch, err = loadWatchOperations(filepath.Join(path, "watch.json"))
if err != nil {
return report, err
}
report.KeyValue, err = loadKeyValueOperations(filepath.Join(path, "operations.json"))
if err != nil {
return report, err
}
return report, nil
}
func loadWatchOperations(path string) (operations []model.WatchOperation, err error) {
_, err = os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
}
file, err := os.OpenFile(path, os.O_RDONLY, 0755)
if err != nil {
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
}
defer file.Close()
decoder := json.NewDecoder(file)
for decoder.More() {
var watch model.WatchOperation
err = decoder.Decode(&watch)
if err != nil {
return nil, fmt.Errorf("failed to decode watch operation, err: %w", err)
}
operations = append(operations, watch)
}
return operations, nil
}
func loadKeyValueOperations(path string) (operations []porcupine.Operation, err error) {
_, err = os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
}
file, err := os.OpenFile(path, os.O_RDONLY, 0755)
if err != nil {
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
}
defer file.Close()
decoder := json.NewDecoder(file)
for decoder.More() {
var operation struct {
ClientID int
Input model.EtcdRequest
Call int64
Output model.MaybeEtcdResponse
Return int64
}
err = decoder.Decode(&operation)
if err != nil {
return nil, fmt.Errorf("failed to decode watch operation, err: %w", err)
}
operations = append(operations, porcupine.Operation{
ClientId: operation.ClientID,
Input: operation.Input,
Call: operation.Call,
Output: operation.Output,
Return: operation.Return,
})
}
return operations, nil
}
func persistWatchOperations(t *testing.T, lg *zap.Logger, path string, responses []model.WatchOperation) {
lg.Info("Saving watch operations", zap.String("path", path))
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
t.Errorf("Failed to save watch operations: %v", err)
return
}
defer file.Close()
encoder := json.NewEncoder(file)
for _, resp := range responses {
err := encoder.Encode(resp)
if err != nil {
t.Errorf("Failed to encode operation: %v", err)
}
}
}
func persistKeyValueOperations(t *testing.T, lg *zap.Logger, path string, operations []porcupine.Operation) {
lg.Info("Saving operation history", zap.String("path", path))
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
t.Errorf("Failed to save operation history: %v", err)
return
}
defer file.Close()
encoder := json.NewEncoder(file)
for _, op := range operations {
err := encoder.Encode(op)
if err != nil {
t.Errorf("Failed to encode operation: %v", err)
}
}
}