From 6f930ca6569ec0bd51d4bbe5e9c0d553694a6e38 Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri <30620077+chhetripradeep@users.noreply.github.com> Date: Sun, 15 Jan 2023 23:37:06 +0800 Subject: [PATCH] Refactor to make exporter-based metrics and query-based metrics optional (#21) Signed-off-by: Pradeep Chhetri --- README.md | 12 ++--- cmd/root.go | 12 ++--- pkg/chtop/chtop.go | 5 +- pkg/model/model.go | 129 +++++++++++++++++++++++++++++++-------------- 4 files changed, 103 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index a3d2eb3..52ad698 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ Usage: chtop [flags] Flags: - --config string config file (default: $HOME/.chtop.yaml) + -c, --config string path of the config file (default: $HOME/.chtop.yaml) -h, --help help for chtop - --metrics-url string clickhouse url for pulling metrics in prometheus exposition format - --queries-database string clickhouse database for connecting clickhouse client (default "system") - --queries-password string clickhouse password for running clickhouse queries - --queries-url string clickhouse url for running clickhouse queries (native protocol port) - --queries-username string clickhouse username for running clickhouse queries (default "default") + -m, --metrics-url string clickhouse url for pulling metrics in prometheus exposition format + -d, --queries-database string clickhouse database for connecting from clickhouse client (default "system") + -p, --queries-password string clickhouse password of the provided clickhouse user for running clickhouse queries + -q, --queries-url string clickhouse endpoint for running clickhouse queries via native protocol + -u, --queries-username string clickhouse username for running clickhouse queries (default "default") ``` Run chtop pointing to prometheus stats endpoint & http endpoint of ClickHouse. diff --git a/cmd/root.go b/cmd/root.go index a3b1ef9..8f59c5a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -40,12 +40,12 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: $HOME/.chtop.yaml)") - rootCmd.PersistentFlags().StringVar(&clickhouseMetricsUrl, "metrics-url", "", "clickhouse url for pulling metrics in prometheus exposition format") - rootCmd.PersistentFlags().StringVar(&clickhouseQueriesUrl, "queries-url", "", "clickhouse url for running clickhouse queries (native protocol port)") - rootCmd.PersistentFlags().StringVar(&clickhouseDatabase, "queries-database", "system", "clickhouse database for connecting clickhouse client") - rootCmd.PersistentFlags().StringVar(&clickhouseUsername, "queries-username", "default", "clickhouse username for running clickhouse queries") - rootCmd.PersistentFlags().StringVar(&clickhousePassword, "queries-password", "", "clickhouse password for running clickhouse queries") + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "path of the config file (default: $HOME/.chtop.yaml)") + rootCmd.PersistentFlags().StringVarP(&clickhouseMetricsUrl, "metrics-url", "m", "", "clickhouse url for pulling metrics in prometheus exposition format") + rootCmd.PersistentFlags().StringVarP(&clickhouseQueriesUrl, "queries-url", "q", "", "clickhouse endpoint for running clickhouse queries via native protocol") + rootCmd.PersistentFlags().StringVarP(&clickhouseDatabase, "queries-database", "d", "system", "clickhouse database for connecting from clickhouse client") + rootCmd.PersistentFlags().StringVarP(&clickhouseUsername, "queries-username", "u", "default", "clickhouse username for running clickhouse queries") + rootCmd.PersistentFlags().StringVarP(&clickhousePassword, "queries-password", "p", "", "clickhouse password of the provided clickhouse user for running clickhouse queries") } func initConfig() { diff --git a/pkg/chtop/chtop.go b/pkg/chtop/chtop.go index 8f100b0..39a7097 100644 --- a/pkg/chtop/chtop.go +++ b/pkg/chtop/chtop.go @@ -4,18 +4,19 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/chhetripradeep/chtop/pkg/query" "github.com/spf13/viper" "github.com/chhetripradeep/chtop/pkg/metric" "github.com/chhetripradeep/chtop/pkg/model" + "github.com/chhetripradeep/chtop/pkg/query" "github.com/chhetripradeep/chtop/pkg/theme" + "github.com/chhetripradeep/chtop/pkg/utils" ) func InitSpinner() spinner.Model { spin := spinner.New() spin.Spinner = spinner.Globe - spin.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + spin.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(utils.ColorNameToHex("magenta"))) return spin } diff --git a/pkg/model/model.go b/pkg/model/model.go index c4053d7..91692b8 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "os" "strconv" @@ -56,7 +57,8 @@ func (e errMsg) Error() string { return e.err.Error() } -func check(url string) tea.Cmd { +// checkUrl checks the http endpoint +func checkUrl(url string) tea.Cmd { return func() tea.Msg { resp, err := http.Get(url) if err != nil { @@ -67,9 +69,27 @@ func check(url string) tea.Cmd { } } +// checkEndpoint checks the tcp endpoint +func checkEndpoint(endpoint string) tea.Cmd { + return func() tea.Msg { + conn, err := net.Dial("tcp", endpoint) + if err != nil { + return errMsg{err} + } + defer conn.Close() + return statusMsg(200) + } +} + // Init inits the bubbletea model for use func (m Model) Init() tea.Cmd { - return check(m.MetricsEndpoint) + if m.MetricsEndpoint != "" { + return checkUrl(m.MetricsEndpoint) + } + if m.QueriesEndpoint != "" { + return checkEndpoint(m.QueriesEndpoint) + } + return nil } // Query hits the prometheus exporter endpoint of clickhouse @@ -180,51 +200,56 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } -func (m Model) UpdateData() tea.Cmd { - return tea.Tick(time.Minute/defaultFps, func(t time.Time) tea.Msg { - for i := range m.ClickHouseMetrics.Metrics { - value, err := m.Query(m.ClickHouseMetrics.Metrics[i].Name) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to query endpoint:", m.MetricsEndpoint, "for metric:", m.ClickHouseMetrics.Metrics[i].Name) - continue - } - - floatValue, err := strconv.ParseFloat(strings.TrimSpace(*value), 64) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to parse value for metric:", m.ClickHouseMetrics.Metrics[i].Name) - continue - } - - m.ClickHouseMetrics.Metrics[i].Update(floatValue) +// UpdateMetricsData updates the data from prometheus exporter's metrics endpoint +func (m Model) UpdateMetricsData() { + for i := range m.ClickHouseMetrics.Metrics { + value, err := m.Query(m.ClickHouseMetrics.Metrics[i].Name) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to query endpoint:", m.MetricsEndpoint, "for metric:", m.ClickHouseMetrics.Metrics[i].Name) + continue } - for i := range m.ClickHouseQueries.Queries { - value, err := m.Execute(m.ClickHouseQueries.Queries[i].Sql) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to execute query:", m.ClickHouseQueries.Queries[i].Name, "at endpoint:", m.QueriesEndpoint) - } + floatValue, err := strconv.ParseFloat(strings.TrimSpace(*value), 64) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to parse value for metric:", m.ClickHouseMetrics.Metrics[i].Name) + continue + } + m.ClickHouseMetrics.Metrics[i].Update(floatValue) + } +} - floatValue, err := strconv.ParseFloat(strings.TrimSpace(*value), 64) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to parse value for metric:", m.ClickHouseQueries.Queries[i].Name) - continue - } +// UpdateQueriesData updates the data from clickhouse queries output +func (m Model) UpdateQueriesData() { + for i := range m.ClickHouseQueries.Queries { + value, err := m.Execute(m.ClickHouseQueries.Queries[i].Sql) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to execute query:", m.ClickHouseQueries.Queries[i].Name, "at endpoint:", m.QueriesEndpoint) + } - m.ClickHouseQueries.Queries[i].Update(floatValue) + floatValue, err := strconv.ParseFloat(strings.TrimSpace(*value), 64) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to parse value for metric:", m.ClickHouseQueries.Queries[i].Name) + continue } + m.ClickHouseQueries.Queries[i].Update(floatValue) + } +} +// UpdateData updates the complete data +func (m Model) UpdateData() tea.Cmd { + return tea.Tick(time.Minute/defaultFps, func(t time.Time) tea.Msg { + if m.MetricsEndpoint != "" { + m.UpdateMetricsData() + } + if m.QueriesEndpoint != "" { + m.UpdateQueriesData() + } return dataMsg(1) }) } -// View shows the current state of the chtop -func (m Model) View() string { - var metricsPlot, queriesPlot, finalPlot string - - if !m.Ready { - return m.Spinner.View() + " Initializing..." - } - +func (m Model) ViewMetricsData() string { + var plot string for i := range m.ClickHouseMetrics.Metrics { caption := m.ClickHouseMetrics.Metrics[i].Alias + fmt.Sprintf(" (Current Value: %.2f)\n\n", m.ClickHouseMetrics.Metrics[i].Latest) @@ -237,13 +262,17 @@ func (m Model) View() string { asciigraph.Caption(caption), ) - metricsPlot += lipgloss.JoinVertical( + plot += lipgloss.JoinVertical( lipgloss.Top, graph, ) - metricsPlot += "\n\n" + plot += "\n\n" } + return plot +} +func (m Model) ViewQueriesData() string { + var plot string for i := range m.ClickHouseQueries.Queries { caption := m.ClickHouseQueries.Queries[i].Name + fmt.Sprintf(" (Current Value: %.2f)\n\n", m.ClickHouseQueries.Queries[i].Latest) @@ -256,11 +285,29 @@ func (m Model) View() string { asciigraph.Caption(caption), ) - queriesPlot += lipgloss.JoinVertical( + plot += lipgloss.JoinVertical( lipgloss.Top, graph, ) - queriesPlot += "\n\n" + plot += "\n\n" + } + return plot +} + +// View shows the current state of the complete data +func (m Model) View() string { + var metricsPlot, queriesPlot, finalPlot string + + // If we don't have data to show in view + if !m.Ready { + return m.Spinner.View() + " Initializing..." + } + + if m.MetricsEndpoint != "" { + metricsPlot = m.ViewMetricsData() + } + if m.QueriesEndpoint != "" { + queriesPlot = m.ViewQueriesData() } finalPlot = lipgloss.JoinHorizontal(