Commit f27b4cf7 authored by ale's avatar ale

Add a debug page for specific datasets

parent 7205304a
Pipeline #8324 passed with stages
in 1 minute and 34 seconds
......@@ -48,11 +48,12 @@ var (
{{else}}
<td colspan="3"></td>
{{end}}
<td>{{.DatasetID}}</td>
<td>{{.DatasetSource}}</td>
<td>{{.DatasetTotalFiles}}</td>
<td>{{humanBytes .DatasetTotalBytes}}
({{humanBytes .DatasetBytesAdded}} new)</td>
<td>{{$d.DatasetID}}</td>
<td><a href="/debug/source?dataset={{$d.DatasetSource}}">
{{$d.DatasetSource}}</a></td>
<td>{{$d.DatasetTotalFiles}}</td>
<td>{{humanBytes $d.DatasetTotalBytes}}
({{humanBytes $d.DatasetBytesAdded}} new)</td>
</tr>
{{end}}
{{end}}
......@@ -63,17 +64,82 @@ var (
</html>`
latestDatasetsTemplate *template.Template
sourceDebugHTML = `<!DOCTYPE html>
<html>
<head>
<title>Tabacco</title>
</head>
<body>
<h1>Tabacco</h1>
<p>Started at {{fmtDate .StartTime}}.</p>
<h3>Source: {{.Source}}</h3>
<table>
<tbody>
<tr>
<td>Latest backup ID</td>
<td>{{.Backup.BackupID}}</td>
</tr>
<tr>
<td>Latest timestamp</td>
<td>{{fmtDate .Backup.BackupTimestamp}}</td>
</tr>
<tr>
<td>Host</td>
<td>{{.Backup.BackupHost}}</td>
</tr>
<tr>
<td>Files in dataset</td>
<td>{{.Dataset.DatasetTotalFiles}}</td>
</tr>
<tr>
<td>Bytes in dataset</td>
<td>{{humanBytes .Dataset.DatasetTotalBytes}}
({{humanBytes .Dataset.DatasetBytesAdded}} new)</td>
</tr>
</tbody>
</table>
<h3>Atoms</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Path</th>
</tr>
</thead>
<tbody>
{{range .Atoms}}
<tr>
<td>{{.AtomName}}</td>
<td>{{.AtomPath}}</td>
</tr>
{{end}}
</tbody>
</table>
</body>
</html>`
sourceDebugTemplate *template.Template
startTime time.Time
)
func init() {
funcs := map[string]interface{}{
"fmtDate": fmtDate,
"humanBytes": func(i int64) string {
return humanize.Bytes(uint64(i))
},
}
latestDatasetsTemplate = template.Must(
template.New("latest").Funcs(map[string]interface{}{
"fmtDate": fmtDate,
"humanBytes": func(i int64) string {
return humanize.Bytes(uint64(i))
},
}).Parse(latestDatasetsHTML))
template.New("latest").Funcs(funcs).Parse(latestDatasetsHTML))
sourceDebugTemplate = template.Must(
template.New("source").Funcs(funcs).Parse(sourceDebugHTML))
startTime = time.Now()
}
......@@ -104,7 +170,7 @@ func (s *httpServer) handleDebug(w http.ResponseWriter, r *http.Request) {
stmt := s.stmts.get(tx, "get_latest_datasets")
defer stmt.Close()
rows, err := stmt.Query(10)
rows, err := stmt.Query(30)
if err != nil {
return err
}
......@@ -150,6 +216,66 @@ func (s *httpServer) handleDebug(w http.ResponseWriter, r *http.Request) {
io.Copy(w, &buf) // nolint
}
func (s *httpServer) handleDebugSource(w http.ResponseWriter, r *http.Request) {
datasetSource := r.FormValue("dataset")
if datasetSource == "" {
http.Error(w, "No dataset specified", http.StatusBadRequest)
return
}
var atoms []*dbAtom
err := retryBusy(r.Context(), func() error {
return withTX(r.Context(), s.db, func(tx *sql.Tx) error {
stmt := s.stmts.get(tx, "get_latest_dataset_by_source")
defer stmt.Close()
rows, err := stmt.Query(datasetSource)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var atom dbAtom
if err := rows.Scan(
&atom.BackupID, &atom.BackupTimestamp, &atom.BackupHost,
&atom.DatasetID, &atom.DatasetSnapshotID, &atom.DatasetSource,
&atom.DatasetTotalFiles, &atom.DatasetTotalBytes,
&atom.DatasetBytesAdded, &atom.DatasetDuration,
&atom.AtomName, &atom.AtomPath, &atom.AtomFullPath,
); err != nil {
return err
}
atoms = append(atoms, &atom)
}
return rows.Err()
})
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(atoms) == 0 {
http.NotFound(w, r)
return
}
var buf bytes.Buffer
if err := sourceDebugTemplate.Execute(&buf, map[string]interface{}{
"Source": datasetSource,
"Atoms": atoms,
"Backup": atoms[0],
"Dataset": atoms[0],
"StartTime": startTime,
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, &buf) // nolint
}
func fmtDate(t time.Time) string {
return t.Format(time.RFC3339)
}
......@@ -21,13 +21,25 @@ func TestDebugPage(t *testing.T) {
httpSrv := &httpServer{svc}
srv := httptest.NewServer(httpSrv.Handler())
// Main debug page.
resp, err := http.Get(srv.URL)
if err != nil {
t.Fatalf("Get(): %v", err)
t.Fatalf("Get(/): %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("Get(): %s", resp.Status)
t.Errorf("Get(/): %s", resp.Status)
io.Copy(os.Stderr, resp.Body) // nolint
}
resp.Body.Close()
// Source debug page.
resp, err = http.Get(srv.URL + "/debug/source?dataset=file")
if err != nil {
t.Fatalf("Get(/debug/source): %v", err)
}
if resp.StatusCode != 200 {
t.Errorf("Get(/debug/source): %s", resp.Status)
io.Copy(os.Stderr, resp.Body) // nolint
}
resp.Body.Close()
}
......@@ -52,6 +52,7 @@ func (s *httpServer) Handler() http.Handler {
m := http.NewServeMux()
m.HandleFunc("/api/add_dataset", s.handleAddDataset)
m.HandleFunc("/api/find_atoms", s.handleFindAtoms)
m.HandleFunc("/debug/source", s.handleDebugSource)
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
......
......@@ -239,6 +239,25 @@ var statements = map[string]string{
dataset_id, dataset_snapshot_id, dataset_source,
dataset_total_files, dataset_total_bytes, dataset_bytes_added,
dataset_duration
ORDER BY backup_timestamp DESC
`,
"get_latest_dataset_by_source": `
SELECT
l.backup_id, l.backup_timestamp, l.backup_host,
l.dataset_id, l.dataset_snapshot_id, l.dataset_source,
l.dataset_total_files, l.dataset_total_bytes, l.dataset_bytes_added,
l.dataset_duration,
l.atom_name, l.atom_path, l.atom_full_path
FROM (
SELECT backup_id, dataset_id, MAX(backup_timestamp)
FROM log
WHERE dataset_source = ?
) AS m
INNER JOIN log AS l
WHERE
m.backup_id = l.backup_id AND
m.dataset_id = l.dataset_id
ORDER BY l.atom_name ASC
`,
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment