mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
250 lines
5.3 KiB
Go
250 lines
5.3 KiB
Go
package thumbs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/live"
|
|
"github.com/grafana/grafana/pkg/services/rendering"
|
|
"github.com/grafana/grafana/pkg/services/search"
|
|
)
|
|
|
|
type dashItem struct {
|
|
uid string
|
|
url string
|
|
}
|
|
|
|
type simpleCrawler struct {
|
|
screenshotsFolder string
|
|
renderService rendering.Service
|
|
threadCount int
|
|
|
|
glive *live.GrafanaLive
|
|
mode CrawlerMode
|
|
opts rendering.Opts
|
|
status crawlStatus
|
|
queue []dashItem
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func newSimpleCrawler(folder string, renderService rendering.Service, gl *live.GrafanaLive) dashRenderer {
|
|
c := &simpleCrawler{
|
|
screenshotsFolder: folder,
|
|
renderService: renderService,
|
|
threadCount: 5,
|
|
glive: gl,
|
|
status: crawlStatus{
|
|
State: "init",
|
|
Complete: 0,
|
|
Queue: 0,
|
|
},
|
|
queue: make([]dashItem, 0),
|
|
}
|
|
c.broadcastStatus()
|
|
return c
|
|
}
|
|
|
|
func (r *simpleCrawler) next() *dashItem {
|
|
if len(r.queue) < 1 {
|
|
return nil
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
v := r.queue[0]
|
|
r.queue = r.queue[1:]
|
|
return &v
|
|
}
|
|
|
|
func (r *simpleCrawler) broadcastStatus() {
|
|
s, err := r.Status()
|
|
if err != nil {
|
|
tlog.Warn("error reading status")
|
|
return
|
|
}
|
|
msg, err := json.Marshal(s)
|
|
if err != nil {
|
|
tlog.Warn("error making message")
|
|
return
|
|
}
|
|
err = r.glive.Publish(r.opts.OrgID, "grafana/broadcast/crawler", msg)
|
|
if err != nil {
|
|
tlog.Warn("error Publish message")
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *simpleCrawler) GetPreview(req *previewRequest) *previewResponse {
|
|
p := getFilePath(r.screenshotsFolder, req)
|
|
if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) {
|
|
return r.queueRender(p, req)
|
|
}
|
|
|
|
return &previewResponse{
|
|
Path: p,
|
|
Code: 200,
|
|
}
|
|
}
|
|
|
|
func (r *simpleCrawler) queueRender(p string, req *previewRequest) *previewResponse {
|
|
go func() {
|
|
fmt.Printf("todo? queue")
|
|
}()
|
|
|
|
return &previewResponse{
|
|
Code: 202,
|
|
Path: p,
|
|
}
|
|
}
|
|
|
|
func (r *simpleCrawler) Start(c *models.ReqContext, mode CrawlerMode, theme rendering.Theme) (crawlStatus, error) {
|
|
if r.status.State == "running" {
|
|
tlog.Info("already running")
|
|
return r.Status()
|
|
}
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
searchQuery := search.Query{
|
|
SignedInUser: c.SignedInUser,
|
|
OrgId: c.OrgId,
|
|
}
|
|
|
|
err := bus.Dispatch(context.Background(), &searchQuery)
|
|
if err != nil {
|
|
return crawlStatus{}, err
|
|
}
|
|
|
|
queue := make([]dashItem, 0, len(searchQuery.Result))
|
|
for _, v := range searchQuery.Result {
|
|
if v.Type == search.DashHitDB {
|
|
queue = append(queue, dashItem{
|
|
uid: v.UID,
|
|
url: v.URL,
|
|
})
|
|
}
|
|
}
|
|
rand.Seed(time.Now().UnixNano())
|
|
rand.Shuffle(len(queue), func(i, j int) { queue[i], queue[j] = queue[j], queue[i] })
|
|
|
|
r.mode = mode
|
|
r.opts = rendering.Opts{
|
|
OrgID: c.OrgId,
|
|
UserID: c.UserId,
|
|
OrgRole: c.OrgRole,
|
|
Theme: theme,
|
|
ConcurrentLimit: 10,
|
|
}
|
|
r.queue = queue
|
|
r.status = crawlStatus{
|
|
Started: time.Now(),
|
|
State: "running",
|
|
Complete: 0,
|
|
}
|
|
r.broadcastStatus()
|
|
|
|
// create a pool of workers
|
|
for i := 0; i < r.threadCount; i++ {
|
|
go r.walk()
|
|
|
|
// wait 1/2 second before starting a new thread
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
r.broadcastStatus()
|
|
return r.Status()
|
|
}
|
|
|
|
func (r *simpleCrawler) Stop() (crawlStatus, error) {
|
|
// cheap hack!
|
|
if r.status.State == "running" {
|
|
r.status.State = "stopping"
|
|
}
|
|
return r.Status()
|
|
}
|
|
|
|
func (r *simpleCrawler) Status() (crawlStatus, error) {
|
|
status := crawlStatus{
|
|
State: r.status.State,
|
|
Started: r.status.Started,
|
|
Complete: r.status.Complete,
|
|
Errors: r.status.Errors,
|
|
Queue: len(r.queue),
|
|
Last: r.status.Last,
|
|
}
|
|
return status, nil
|
|
}
|
|
|
|
func (r *simpleCrawler) walk() {
|
|
for {
|
|
if r.status.State == "stopping" {
|
|
break
|
|
}
|
|
|
|
item := r.next()
|
|
if item == nil {
|
|
break
|
|
}
|
|
|
|
tlog.Info("GET THUMBNAIL", "url", item.url)
|
|
|
|
// Hack (for now) pick a URL that will render
|
|
panelURL := strings.TrimPrefix(item.url, "/") + "?kiosk"
|
|
res, err := r.renderService.Render(context.Background(), rendering.Opts{
|
|
Width: 320,
|
|
Height: 240,
|
|
Path: panelURL,
|
|
OrgID: r.opts.OrgID,
|
|
UserID: r.opts.UserID,
|
|
ConcurrentLimit: r.opts.ConcurrentLimit,
|
|
OrgRole: r.opts.OrgRole,
|
|
Theme: r.opts.Theme,
|
|
Timeout: 10 * time.Second,
|
|
DeviceScaleFactor: -5, // negative numbers will render larger then scale down
|
|
})
|
|
if err != nil {
|
|
tlog.Warn("error getting image", "err", err)
|
|
r.status.Errors++
|
|
} else if res.FilePath == "" {
|
|
tlog.Warn("error getting image... no response")
|
|
r.status.Errors++
|
|
} else if strings.Contains(res.FilePath, "public/img") {
|
|
tlog.Warn("error getting image... internal result", "img", res.FilePath)
|
|
r.status.Errors++
|
|
} else {
|
|
p := getFilePath(r.screenshotsFolder, &previewRequest{
|
|
UID: item.uid,
|
|
OrgID: r.opts.OrgID,
|
|
Theme: r.opts.Theme,
|
|
Size: PreviewSizeThumb,
|
|
})
|
|
err = os.Rename(res.FilePath, p)
|
|
if err != nil {
|
|
r.status.Errors++
|
|
tlog.Warn("error moving image", "err", err)
|
|
} else {
|
|
r.status.Complete++
|
|
tlog.Info("saved thumbnail", "img", item.url)
|
|
}
|
|
}
|
|
|
|
time.Sleep(5 * time.Second)
|
|
r.status.Last = time.Now()
|
|
r.broadcastStatus()
|
|
}
|
|
|
|
r.status.State = "stopped"
|
|
r.status.Finished = time.Now()
|
|
r.broadcastStatus()
|
|
}
|