fix: import path should be github.com/ooni/probe-cli/v3 (#200)

See https://github.com/ooni/probe/issues/1335#issuecomment-771499511
This commit is contained in:
Simone Basso
2021-02-02 10:32:46 +01:00
committed by GitHub
parent faa9308b1e
commit b1ce300c8d
68 changed files with 86 additions and 85 deletions
@@ -0,0 +1,33 @@
package batch
import (
j "encoding/json"
"io"
"os"
"sync"
"github.com/apex/log"
)
// Default handler outputting to stderr.
var Default = New(os.Stderr)
// Handler implementation.
type Handler struct {
*j.Encoder
mu sync.Mutex
}
// New handler.
func New(w io.Writer) *Handler {
return &Handler{
Encoder: j.NewEncoder(w),
}
}
// HandleLog implements log.Handler.
func (h *Handler) HandleLog(e *log.Entry) error {
h.mu.Lock()
defer h.mu.Unlock()
return h.Encoder.Encode(e)
}
@@ -0,0 +1,173 @@
package cli
import (
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"github.com/apex/log"
"github.com/fatih/color"
colorable "github.com/mattn/go-colorable"
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
)
// Default handler outputting to stderr.
var Default = New(os.Stdout)
// start time.
var start = time.Now()
var bold = color.New(color.Bold)
// Colors mapping.
var Colors = [...]*color.Color{
log.DebugLevel: color.New(color.FgWhite),
log.InfoLevel: color.New(color.FgBlue),
log.WarnLevel: color.New(color.FgYellow),
log.ErrorLevel: color.New(color.FgRed),
log.FatalLevel: color.New(color.FgRed),
}
// Strings mapping.
var Strings = [...]string{
log.DebugLevel: "•",
log.InfoLevel: "•",
log.WarnLevel: "•",
log.ErrorLevel: "",
log.FatalLevel: "",
}
// Handler implementation.
type Handler struct {
mu sync.Mutex
Writer io.Writer
Padding int
}
// New handler.
func New(w io.Writer) *Handler {
if f, ok := w.(*os.File); ok {
return &Handler{
Writer: colorable.NewColorable(f),
Padding: 3,
}
}
return &Handler{
Writer: w,
Padding: 3,
}
}
func logSectionTitle(w io.Writer, f log.Fields) error {
colWidth := 24
title := f.Get("title").(string)
fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth+2)+"┓\n")
fmt.Fprintf(w, "┃ %s ┃\n", utils.RightPad(title, colWidth))
fmt.Fprintf(w, "┗"+strings.Repeat("━", colWidth+2)+"┛\n")
return nil
}
func logTable(w io.Writer, f log.Fields) error {
color := color.New(color.FgBlue)
names := f.Names()
var lines []string
colWidth := 0
for _, name := range names {
if name == "type" {
continue
}
line := fmt.Sprintf("%s: %s", color.Sprint(name), f.Get(name))
lineLength := utils.EscapeAwareRuneCountInString(line)
lines = append(lines, line)
if colWidth < lineLength {
colWidth = lineLength
}
}
fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth+2)+"┓\n")
for _, line := range lines {
fmt.Fprintf(w, "┃ %s ┃\n",
utils.RightPad(line, colWidth),
)
}
fmt.Fprintf(w, "┗"+strings.Repeat("━", colWidth+2)+"┛\n")
return nil
}
// TypedLog is used for handling special "typed" logs to the CLI
func (h *Handler) TypedLog(t string, e *log.Entry) error {
switch t {
case "engine":
fmt.Fprintf(h.Writer, "[engine] %s\n", e.Message)
return nil
case "progress":
perc := e.Fields.Get("percentage").(float64) * 100
eta := e.Fields.Get("eta").(float64)
var etaMessage string
if eta >= 0 {
etaMessage = fmt.Sprintf("(%ss left)", bold.Sprintf("%.2f", eta))
}
s := fmt.Sprintf(" %s %-25s %s",
bold.Sprintf("%.2f%%", perc),
e.Message, etaMessage)
fmt.Fprint(h.Writer, s)
fmt.Fprintln(h.Writer)
return nil
case "table":
return logTable(h.Writer, e.Fields)
case "measurement_item":
return logMeasurementItem(h.Writer, e.Fields)
case "measurement_json":
return logMeasurementJSON(h.Writer, e.Fields)
case "measurement_summary":
return logMeasurementSummary(h.Writer, e.Fields)
case "result_item":
return logResultItem(h.Writer, e.Fields)
case "result_summary":
return logResultSummary(h.Writer, e.Fields)
case "section_title":
return logSectionTitle(h.Writer, e.Fields)
default:
return h.DefaultLog(e)
}
}
// DefaultLog is the default way of printing out logs
func (h *Handler) DefaultLog(e *log.Entry) error {
color := Colors[e.Level]
level := Strings[e.Level]
names := e.Fields.Names()
s := color.Sprintf("%s %-25s", bold.Sprintf("%*s", h.Padding+1, level), e.Message)
for _, name := range names {
if name == "source" {
continue
}
s += fmt.Sprintf(" %s=%v", color.Sprint(name), e.Fields.Get(name))
}
fmt.Fprint(h.Writer, s)
fmt.Fprintln(h.Writer)
return nil
}
// HandleLog implements log.Handler.
func (h *Handler) HandleLog(e *log.Entry) error {
h.mu.Lock()
defer h.mu.Unlock()
t, isTyped := e.Fields["type"].(string)
if isTyped {
return h.TypedLog(t, e)
}
return h.DefaultLog(e)
}
@@ -0,0 +1,142 @@
package cli
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/apex/log"
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
)
func statusIcon(ok bool) string {
if ok {
return "✓"
}
return "❌"
}
func logTestKeys(w io.Writer, testKeys string) error {
colWidth := 24
var out bytes.Buffer
if err := json.Indent(&out, []byte(testKeys), "", " "); err != nil {
return err
}
testKeysLines := strings.Split(string(out.Bytes()), "\n")
if len(testKeysLines) > 1 {
testKeysLines = testKeysLines[1 : len(testKeysLines)-1]
testKeysLines[0] = "{" + testKeysLines[0][1:]
testKeysLines[len(testKeysLines)-1] = testKeysLines[len(testKeysLines)-1] + "}"
}
for _, line := range testKeysLines {
fmt.Fprintf(w, fmt.Sprintf("│ %s │\n",
utils.RightPad(line, colWidth*2)))
}
return nil
}
func logMeasurementItem(w io.Writer, f log.Fields) error {
colWidth := 24
rID := f.Get("id").(int64)
testName := f.Get("test_name").(string)
// We currently don't use these fields in the view
//testGroupName := f.Get("test_group_name").(string)
//networkName := f.Get("network_name").(string)
//asn := fmt.Sprintf("AS%d (%s)", f.Get("asn").(uint), f.Get("network_country_code").(string))
testKeys := f.Get("test_keys").(string)
isAnomaly := f.Get("is_anomaly").(bool)
isFailed := f.Get("is_failed").(bool)
isUploaded := f.Get("is_uploaded").(bool)
url := f.Get("url").(string)
urlCategoryCode := f.Get("url_category_code").(string)
isFirst := f.Get("is_first").(bool)
isLast := f.Get("is_last").(bool)
if isFirst {
fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth*2+2)+"┓\n")
} else {
fmt.Fprintf(w, "┢"+strings.Repeat("━", colWidth*2+2)+"┪\n")
}
anomalyStr := fmt.Sprintf("ok: %s", statusIcon(!isAnomaly))
uploadStr := fmt.Sprintf("uploaded: %s", statusIcon(isUploaded))
failureStr := fmt.Sprintf("success: %s", statusIcon(!isFailed))
fmt.Fprintf(w, fmt.Sprintf("│ %s │\n",
utils.RightPad(
fmt.Sprintf("#%d", rID), colWidth*2)))
if url != "" {
fmt.Fprintf(w, fmt.Sprintf("│ %s │\n",
utils.RightPad(
fmt.Sprintf("%s (%s)", url, urlCategoryCode), colWidth*2)))
}
fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
utils.RightPad(testName, colWidth),
utils.RightPad(anomalyStr, colWidth)))
fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
utils.RightPad(failureStr, colWidth),
utils.RightPad(uploadStr, colWidth)))
if testKeys != "" {
if err := logTestKeys(w, testKeys); err != nil {
return err
}
}
if isLast {
fmt.Fprintf(w, "└┬────────────────────────────────────────────────┬┘\n")
}
return nil
}
func logMeasurementSummary(w io.Writer, f log.Fields) error {
colWidth := 12
totalCount := f.Get("total_count").(int64)
anomalyCount := f.Get("anomaly_count").(int64)
totalRuntime := f.Get("total_runtime").(float64)
dataUp := f.Get("data_usage_up").(float64)
dataDown := f.Get("data_usage_down").(float64)
startTime := f.Get("start_time").(time.Time)
asn := f.Get("asn").(uint)
countryCode := f.Get("network_country_code").(string)
networkName := f.Get("network_name").(string)
fmt.Fprintf(w, " │ %s │\n",
utils.RightPad(startTime.Format(time.RFC822), (colWidth+3)*3),
)
fmt.Fprintf(w, " │ %s │\n",
utils.RightPad(fmt.Sprintf("AS%d, %s (%s)", asn, networkName, countryCode), (colWidth+3)*3),
)
fmt.Fprintf(w, " │ %s %s %s │\n",
utils.RightPad(fmt.Sprintf("%.2fs", totalRuntime), colWidth),
utils.RightPad(fmt.Sprintf("%d/%d anmls", anomalyCount, totalCount), colWidth),
utils.RightPad(fmt.Sprintf("⬆ %s ⬇ %s", formatSize(dataUp), formatSize(dataDown)), colWidth+4))
fmt.Fprintf(w, " └────────────────────────────────────────────────┘\n")
return nil
}
func logMeasurementJSON(w io.Writer, f log.Fields) error {
m := f.Get("measurement_json").(map[string]interface{})
json, err := json.MarshalIndent(m, "", " ")
if err != nil {
return err
}
fmt.Fprintf(w, string(json))
return nil
}
@@ -0,0 +1,126 @@
// Package progress provides a simple terminal progress bar.
package progress
import (
"bytes"
"fmt"
"html/template"
"io"
"math"
"strings"
)
// Bar is a progress bar.
type Bar struct {
StartDelimiter string // StartDelimiter for the bar ("|").
EndDelimiter string // EndDelimiter for the bar ("|").
Filled string // Filled section representation ("█").
Empty string // Empty section representation ("░")
Total float64 // Total value.
Width int // Width of the bar.
value float64
tmpl *template.Template
text string
}
// New returns a new bar with the given total.
func New(total float64) *Bar {
b := &Bar{
StartDelimiter: "|",
EndDelimiter: "|",
Filled: "█",
Empty: "░",
Total: total,
Width: 60,
}
b.Template(`{{.Percent | printf "%3.0f"}}% {{.Bar}} {{.Text}}`)
return b
}
// NewInt returns a new bar with the given total.
func NewInt(total int) *Bar {
return New(float64(total))
}
// Text sets the text value.
func (b *Bar) Text(s string) {
b.text = s
}
// Value sets the value.
func (b *Bar) Value(n float64) {
if n > b.Total {
panic("Bar update value cannot be greater than the total")
}
b.value = n
}
// ValueInt sets the value.
func (b *Bar) ValueInt(n int) {
b.Value(float64(n))
}
// Percent returns the percentage
func (b *Bar) percent() float64 {
return (b.value / b.Total) * 100
}
// Bar returns the progress bar string.
func (b *Bar) bar() string {
p := b.value / b.Total
filled := math.Ceil(float64(b.Width) * p)
empty := math.Floor(float64(b.Width) - filled)
s := b.StartDelimiter
s += strings.Repeat(b.Filled, int(filled))
s += strings.Repeat(b.Empty, int(empty))
s += b.EndDelimiter
return s
}
// String returns the progress bar.
func (b *Bar) String() string {
var buf bytes.Buffer
data := struct {
Value float64
Total float64
Percent float64
StartDelimiter string
EndDelimiter string
Bar string
Text string
}{
Value: b.value,
Text: b.text,
StartDelimiter: b.StartDelimiter,
EndDelimiter: b.EndDelimiter,
Percent: b.percent(),
Bar: b.bar(),
}
if err := b.tmpl.Execute(&buf, data); err != nil {
panic(err)
}
return buf.String()
}
// WriteTo writes the progress bar to w.
func (b *Bar) WriteTo(w io.Writer) (int64, error) {
s := fmt.Sprintf("\r %s ", b.String())
_, err := io.WriteString(w, s)
return int64(len(s)), err
}
// Template for rendering. This method will panic if the template fails to parse.
func (b *Bar) Template(s string) {
t, err := template.New("").Parse(s)
if err != nil {
panic(err)
}
b.tmpl = t
}
@@ -0,0 +1,154 @@
package cli
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/apex/log"
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
)
func formatSpeed(speed float64) string {
if speed < 1000 {
return fmt.Sprintf("%.2f Kbit/s", speed)
} else if speed < 1000*1000 {
return fmt.Sprintf("%.2f Mbit/s", float32(speed)/1000)
} else if speed < 1000*1000*1000 {
return fmt.Sprintf("%.2f Gbit/s", float32(speed)/(1000*1000))
}
// WTF, you crazy?
return fmt.Sprintf("%.2f Tbit/s", float32(speed)/(1000*1000*1000))
}
func formatSize(size float64) string {
if size < 1024 {
return fmt.Sprintf("%.1fK", size)
} else if size < 1024*1024 {
return fmt.Sprintf("%.1fM", size/1024.0)
} else if size < 1024*1024*1024 {
return fmt.Sprintf("%.1fG", size/(1024.0*1024.0))
}
// WTF, you crazy?
return fmt.Sprintf("%.1fT", size/(1024*1024*1024))
}
var summarizers = map[string]func(uint64, uint64, string) []string{
"websites": func(totalCount uint64, anomalyCount uint64, ss string) []string {
return []string{
fmt.Sprintf("%d tested", totalCount),
fmt.Sprintf("%d blocked", anomalyCount),
"",
}
},
"performance": func(totalCount uint64, anomalyCount uint64, ss string) []string {
var tk database.PerformanceTestKeys
if err := json.Unmarshal([]byte(ss), &tk); err != nil {
return nil
}
return []string{
fmt.Sprintf("Download: %s", formatSpeed(tk.Download)),
fmt.Sprintf("Upload: %s", formatSpeed(tk.Upload)),
fmt.Sprintf("Ping: %.2fms", tk.Ping),
}
},
"im": func(totalCount uint64, anomalyCount uint64, ss string) []string {
return []string{
fmt.Sprintf("%d tested", totalCount),
fmt.Sprintf("%d blocked", anomalyCount),
"",
}
},
"middlebox": func(totalCount uint64, anomalyCount uint64, ss string) []string {
return []string{
fmt.Sprintf("Detected: %v", anomalyCount > 0),
"",
"",
}
},
"circumvention": func(totalCount uint64, anomalyCount uint64, ss string) []string {
return []string{
fmt.Sprintf("%d tested", totalCount),
fmt.Sprintf("%d blocked", anomalyCount),
"",
}
},
}
func makeSummary(name string, totalCount uint64, anomalyCount uint64, ss string) []string {
return summarizers[name](totalCount, anomalyCount, ss)
}
func logResultItem(w io.Writer, f log.Fields) error {
colWidth := 24
rID := f.Get("id").(int64)
name := f.Get("name").(string)
isDone := f.Get("is_done").(bool)
startTime := f.Get("start_time").(time.Time)
networkName := f.Get("network_name").(string)
asn := fmt.Sprintf("AS%d (%s)", f.Get("asn").(uint), f.Get("network_country_code").(string))
//runtime := f.Get("runtime").(float64)
//dataUsageUp := f.Get("dataUsageUp").(int64)
//dataUsageDown := f.Get("dataUsageDown").(int64)
index := f.Get("index").(int)
totalCount := f.Get("total_count").(int)
if index == 0 {
fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth*2+2)+"┓\n")
} else {
fmt.Fprintf(w, "┢"+strings.Repeat("━", colWidth*2+2)+"┪\n")
}
firstRow := utils.RightPad(fmt.Sprintf("#%d - %s", rID, startTime.Format(time.RFC822)), colWidth*2)
fmt.Fprintf(w, "┃ "+firstRow+" ┃\n")
fmt.Fprintf(w, "┡"+strings.Repeat("━", colWidth*2+2)+"┩\n")
summary := makeSummary(name,
f.Get("measurement_count").(uint64),
f.Get("measurement_anomaly_count").(uint64),
f.Get("test_keys").(string))
fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
utils.RightPad(name, colWidth),
utils.RightPad(summary[0], colWidth)))
fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
utils.RightPad(networkName, colWidth),
utils.RightPad(summary[1], colWidth)))
fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
utils.RightPad(asn, colWidth),
utils.RightPad(summary[2], colWidth)))
if index == totalCount-1 {
if isDone == true {
fmt.Fprintf(w, "└┬──────────────┬─────────────┬───────────────────┬┘\n")
} else {
// We want the incomplete section to not have a footer
fmt.Fprintf(w, "└──────────────────────────────────────────────────┘\n")
}
}
return nil
}
func logResultSummary(w io.Writer, f log.Fields) error {
networks := f.Get("total_networks").(int64)
tests := f.Get("total_tests").(int64)
dataUp := f.Get("total_data_usage_up").(float64)
dataDown := f.Get("total_data_usage_down").(float64)
if tests == 0 {
fmt.Fprintf(w, "No results\n")
fmt.Fprintf(w, "Try running:\n")
fmt.Fprintf(w, " ooniprobe run websites\n")
return nil
}
// └┬──────────────┬─────────────┬───────────────┬
fmt.Fprintf(w, " │ %s │ %s │ %s │\n",
utils.RightPad(fmt.Sprintf("%d tests", tests), 12),
utils.RightPad(fmt.Sprintf("%d nets", networks), 11),
utils.RightPad(fmt.Sprintf("⬆ %s ⬇ %s", formatSize(dataUp), formatSize(dataDown)), 17))
fmt.Fprintf(w, " └──────────────┴─────────────┴───────────────────┘\n")
return nil
}
@@ -0,0 +1,39 @@
#ifndef _WIN32
#include <syslog.h>
#endif
void ooniprobe_openlog(void) {
#ifndef _WIN32
(void)openlog("ooniprobe", LOG_PID, LOG_USER);
#endif
}
void ooniprobe_log_debug(const char *message) {
#ifndef _WIN32
(void)syslog(LOG_DEBUG, "%s", message);
#endif
}
void ooniprobe_log_info(const char *message) {
#ifndef _WIN32
(void)syslog(LOG_INFO, "%s", message);
#endif
}
void ooniprobe_log_warning(const char *message) {
#ifndef _WIN32
(void)syslog(LOG_WARNING, "%s", message);
#endif
}
void ooniprobe_log_err(const char *message) {
#ifndef _WIN32
(void)syslog(LOG_ERR, "%s", message);
#endif
}
void ooniprobe_log_crit(const char *message) {
#ifndef _WIN32
(void)syslog(LOG_CRIT, "%s", message);
#endif
}
@@ -0,0 +1,53 @@
// Package syslog contains a syslog handler.
//
// We use this handler on macOS systems to log messages
// when ooniprobe is running in the background.
package syslog
import (
"fmt"
"unsafe"
"github.com/apex/log"
)
/*
#include<stdlib.h>
void ooniprobe_openlog(void);
void ooniprobe_log_debug(const char *message);
void ooniprobe_log_info(const char *message);
void ooniprobe_log_warning(const char *message);
void ooniprobe_log_err(const char *message);
void ooniprobe_log_crit(const char *message);
*/
import "C"
// Default is the handler that emits logs with syslog
var Default log.Handler = newhandler()
type handler struct{}
func newhandler() handler {
C.ooniprobe_openlog()
return handler{}
}
func (h handler) HandleLog(e *log.Entry) error {
message := fmt.Sprintf("%s %+v", e.Message, e.Fields)
cstr := C.CString(message)
defer C.free(unsafe.Pointer(cstr))
switch e.Level {
case log.DebugLevel:
C.ooniprobe_log_debug(cstr)
case log.InfoLevel:
C.ooniprobe_log_info(cstr)
case log.WarnLevel:
C.ooniprobe_log_warning(cstr)
case log.ErrorLevel:
C.ooniprobe_log_err(cstr)
default:
C.ooniprobe_log_crit(cstr)
}
return nil
}