-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: metrics RPC endpoint and parser
- Loading branch information
Showing
5 changed files
with
262 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,70 @@ | ||
package internal | ||
|
||
import "time" | ||
import ( | ||
"context" | ||
"errors" | ||
"github.com/algorandfoundation/hack-tui/api" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type MetricsModel struct { | ||
Enabled bool | ||
Window int | ||
RoundTime time.Duration | ||
TPS float64 | ||
RX int | ||
TX int | ||
} | ||
|
||
type MetricsResponse map[string]int | ||
|
||
func parseMetricsContent(content string) (MetricsResponse, error) { | ||
var err error | ||
result := MetricsResponse{} | ||
|
||
// Validate the Content | ||
var isValid bool | ||
isValid, err = regexp.MatchString(`^#`, content) | ||
isValid = isValid && err == nil && content != "" | ||
if !isValid { | ||
return nil, errors.New("invalid metrics content") | ||
} | ||
|
||
// Regex for Metrics Format, | ||
// selects all content that does not start with # in multiline mode | ||
re := regexp.MustCompile(`(?m)^[^#].*`) | ||
rows := re.FindAllString(content, -1) | ||
|
||
// Add the strings to the map | ||
for _, row := range rows { | ||
var value int | ||
keyValue := strings.Split(row, " ") | ||
value, err = strconv.Atoi(keyValue[1]) | ||
result[keyValue[0]] = value | ||
} | ||
|
||
// Handle any error results | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Give the user what they asked for | ||
return result, nil | ||
} | ||
|
||
// GetMetrics parses the /metrics endpoint from algod into a map | ||
func GetMetrics(ctx context.Context, client *api.ClientWithResponses) (MetricsResponse, error) { | ||
res, err := client.MetricsWithResponse(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if res.StatusCode() != 200 { | ||
return nil, errors.New("invalid status code") | ||
} | ||
|
||
return parseMetricsContent(string(res.Body)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"github.com/algorandfoundation/hack-tui/api" | ||
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func Test_GetMetrics(t *testing.T) { | ||
// Setup elevated client | ||
apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
client, err := api.NewClientWithResponses("http://localhost:4001", api.WithRequestEditorFn(apiToken.Intercept)) | ||
|
||
metrics, err := GetMetrics(context.Background(), client) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// TODO: ensure localnet is running before tests | ||
metrics, err = GetMetrics(context.Background(), client) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if metrics["algod_agreement_dropped"] != 0 { | ||
t.Fatal(strconv.Itoa(metrics["algod_agreement_dropped"]) + " is not zero") | ||
} | ||
} | ||
|
||
func Test_parseMetrics(t *testing.T) { | ||
content := `# HELP algod_telemetry_drops_total telemetry messages dropped due to full queues | ||
# TYPE algod_telemetry_drops_total counter | ||
algod_telemetry_drops_total 0 | ||
# HELP algod_telemetry_errs_total telemetry messages dropped due to server error | ||
# TYPE algod_telemetry_errs_total counter | ||
algod_telemetry_errs_total 0 | ||
# HELP algod_ram_usage number of bytes runtime.ReadMemStats().HeapInuse | ||
# TYPE algod_ram_usage gauge | ||
algod_ram_usage 0 | ||
# HELP algod_crypto_vrf_generate_total Total number of calls to GenerateVRFSecrets | ||
# TYPE algod_crypto_vrf_generate_total counter | ||
algod_crypto_vrf_generate_total 0 | ||
# HELP algod_crypto_vrf_prove_total Total number of calls to VRFSecrets.Prove | ||
# TYPE algod_crypto_vrf_prove_total counter | ||
algod_crypto_vrf_prove_total 0 | ||
# HELP algod_crypto_vrf_hash_total Total number of calls to VRFProof.Hash | ||
# TYPE algod_crypto_vrf_hash_total counter | ||
algod_crypto_vrf_hash_total 0 | ||
` | ||
metrics, err := parseMetricsContent(content) | ||
|
||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if metrics["algod_telemetry_drops_total"] != 0 { | ||
t.Fatal(strconv.Itoa(metrics["algod_telemetry_drops_total"]) + " is not 0") | ||
} | ||
|
||
content = `INVALID` | ||
_, err = parseMetricsContent(content) | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
|
||
content = `# HELP algod_telemetry_drops_total telemetry messages dropped due to full queues | ||
# TYPE algod_telemetry_drops_total counter | ||
algod_telemetry_drops_total NAN` | ||
_, err = parseMetricsContent(content) | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"github.com/algorandfoundation/hack-tui/api" | ||
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func Test_StateModel(t *testing.T) { | ||
// Setup elevated client | ||
apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
client, err := api.NewClientWithResponses("http://localhost:8080", api.WithRequestEditorFn(apiToken.Intercept)) | ||
|
||
state := StateModel{ | ||
Watching: true, | ||
Status: StatusModel{ | ||
LastRound: 1337, | ||
NeedsUpdate: true, | ||
State: "SYNCING", | ||
}, | ||
Metrics: MetricsModel{ | ||
RoundTime: 0, | ||
TX: 0, | ||
RX: 0, | ||
TPS: 0, | ||
}, | ||
} | ||
count := 0 | ||
go state.Watch(func(model *StateModel, err error) { | ||
if err != nil || model == nil { | ||
t.Error("Failed") | ||
return | ||
} | ||
count++ | ||
}, context.Background(), client) | ||
time.Sleep(5 * time.Second) | ||
// Stop the watcher | ||
state.Stop() | ||
if count == 0 { | ||
t.Fatal("Did not receive any updates") | ||
} | ||
if state.Status.LastRound <= 0 { | ||
t.Fatal("LastRound is stale") | ||
} | ||
t.Log( | ||
"Watching: ", state.Watching, | ||
"LastRound: ", state.Status.LastRound, | ||
"NeedsUpdate: ", state.Status.NeedsUpdate, | ||
"State: ", state.Status.State, | ||
"RoundTime: ", state.Metrics.RoundTime, | ||
"RX: ", state.Metrics.RX, | ||
"TX: ", state.Metrics.TX, | ||
) | ||
} |