diff --git a/agent/go.mod b/agent/go.mod index 10cf72e1e..8296d508b 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-playground/validator/v10 v10.5.0 github.com/google/uuid v1.1.2 github.com/gorilla/websocket v1.4.2 + github.com/nav-inc/datetime v0.1.3 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231 github.com/ory/keto-client-go v0.7.0-alpha.1 diff --git a/agent/go.sum b/agent/go.sum index 4d036ad33..4b2f13c91 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -515,6 +515,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nav-inc/datetime v0.1.3 h1:PaybPUsScX+Cd3TEa1tYpfwU61deCEhMTlCO2hONm1c= +github.com/nav-inc/datetime v0.1.3/go.mod h1:gKGf5G+cW7qkTo5TC/sieNyz6lYdrA9cf1PNV+pXIOE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= @@ -612,6 +614,7 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/agent/pkg/api/main.go b/agent/pkg/api/main.go index 89854b4b6..a6228ae6b 100644 --- a/agent/pkg/api/main.go +++ b/agent/pkg/api/main.go @@ -140,7 +140,8 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension mizuEntry.Rules = rules } - oas.GetOasGeneratorInstance().PushEntry(harEntry) + entryWSource := oas.EntryWithSource{Entry: *harEntry, Source: mizuEntry.Source.Name} + oas.GetOasGeneratorInstance().PushEntry(&entryWSource) } data, err := json.Marshal(mizuEntry) diff --git a/agent/pkg/oas/counters.go b/agent/pkg/oas/counters.go new file mode 100644 index 000000000..cd3b1b328 --- /dev/null +++ b/agent/pkg/oas/counters.go @@ -0,0 +1,57 @@ +package oas + +import "math" + +type Counter struct { + Entries int `json:"entries"` + Failures int `json:"failures"` + FirstSeen float64 `json:"firstSeen"` + LastSeen float64 `json:"lastSeen"` + SumRT float64 `json:"sumRT"` + SumDuration float64 `json:"sumDuration"` +} + +func (c *Counter) addEntry(ts float64, rt float64, succ bool, dur float64) { + c.Entries += 1 + c.SumRT += rt + c.SumDuration += dur + if !succ { + c.Failures += 1 + } + + if c.FirstSeen == 0 { + c.FirstSeen = ts + } else { + c.FirstSeen = math.Min(c.FirstSeen, ts) + } + + c.LastSeen = math.Max(c.LastSeen, ts) +} + +func (c *Counter) addOther(other *Counter) { + c.Entries += other.Entries + c.SumRT += other.SumRT + c.Failures += other.Failures + c.SumDuration += other.SumDuration + + if c.FirstSeen == 0 { + c.FirstSeen = other.FirstSeen + } else { + c.FirstSeen = math.Min(c.FirstSeen, other.FirstSeen) + } + + c.LastSeen = math.Max(c.LastSeen, other.LastSeen) +} + +type CounterMap map[string]*Counter + +func (m *CounterMap) addOther(other *CounterMap) { + for src, cmap := range *other { + if existing, ok := (*m)[src]; ok { + existing.addOther(cmap) + } else { + copied := *cmap + (*m)[src] = &copied + } + } +} diff --git a/agent/pkg/oas/feeder_test.go b/agent/pkg/oas/feeder_test.go index 228253024..c5fb0c753 100644 --- a/agent/pkg/oas/feeder_test.go +++ b/agent/pkg/oas/feeder_test.go @@ -110,25 +110,26 @@ func feedFromHAR(file string, isSync bool) (int, error) { cnt := 0 for _, entry := range harDoc.Log.Entries { cnt += 1 - feedEntry(&entry, isSync) + feedEntry(&entry, "", isSync) } return cnt, nil } -func feedEntry(entry *har.Entry, isSync bool) { +func feedEntry(entry *har.Entry, source string, isSync bool) { if entry.Response.Status == 302 { logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime) } - if strings.Contains(entry.Request.URL, "taboola") { + if strings.Contains(entry.Request.URL, "some") { // for debugging logger.Log.Debugf("Interesting: %s", entry.Request.URL) } + ews := EntryWithSource{Entry: *entry, Source: source} if isSync { - GetOasGeneratorInstance().entriesChan <- *entry // blocking variant, right? + GetOasGeneratorInstance().entriesChan <- ews // blocking variant, right? } else { - GetOasGeneratorInstance().PushEntry(entry) + GetOasGeneratorInstance().PushEntry(&ews) } } @@ -145,6 +146,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) { var meta map[string]interface{} buf := strings.Builder{} cnt := 0 + source := "" for { substr, isPrefix, err := reader.ReadLine() if err == io.EOF { @@ -164,6 +166,9 @@ func feedFromLDJSON(file string, isSync bool) (int, error) { if err != nil { return 0, err } + if s, ok := meta["_source"]; ok && s != nil { + source = s.(string) + } } else { var entry har.Entry err := json.Unmarshal([]byte(line), &entry) @@ -171,7 +176,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) { logger.Log.Warningf("Failed decoding entry: %s", line) } else { cnt += 1 - feedEntry(&entry, isSync) + feedEntry(&entry, source, isSync) } } } diff --git a/agent/pkg/oas/oas_generator.go b/agent/pkg/oas/oas_generator.go index 771f2d622..27cb01671 100644 --- a/agent/pkg/oas/oas_generator.go +++ b/agent/pkg/oas/oas_generator.go @@ -30,7 +30,7 @@ func (g *oasGenerator) Start() { ctx, cancel := context.WithCancel(context.Background()) g.cancel = cancel g.ctx = ctx - g.entriesChan = make(chan har.Entry, 100) // buffer up to 100 entries for OAS processing + g.entriesChan = make(chan EntryWithSource, 100) // buffer up to 100 entries for OAS processing g.ServiceSpecs = &sync.Map{} g.started = true go instance.runGeneretor() @@ -43,11 +43,12 @@ func (g *oasGenerator) runGeneretor() { logger.Log.Infof("OAS Generator was canceled") return - case entry, ok := <-g.entriesChan: + case entryWithSource, ok := <-g.entriesChan: if !ok { logger.Log.Infof("OAS Generator - entries channel closed") break } + entry := entryWithSource.Entry u, err := url.Parse(entry.Request.URL) if err != nil { logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err) @@ -62,7 +63,7 @@ func (g *oasGenerator) runGeneretor() { gen = val.(*SpecGen) } - opId, err := gen.feedEntry(entry) + opId, err := gen.feedEntry(entryWithSource) if err != nil { txt, suberr := json.Marshal(entry) if suberr == nil { @@ -78,12 +79,12 @@ func (g *oasGenerator) runGeneretor() { } } -func (g *oasGenerator) PushEntry(entry *har.Entry) { +func (g *oasGenerator) PushEntry(entryWithSource *EntryWithSource) { if !g.started { return } select { - case g.entriesChan <- *entry: + case g.entriesChan <- *entryWithSource: default: logger.Log.Warningf("OAS Generator - entry wasn't sent to channel because the channel has no buffer or there is no receiver") } @@ -99,10 +100,15 @@ func newOasGenerator() *oasGenerator { } } +type EntryWithSource struct { + Source string + Entry har.Entry +} + type oasGenerator struct { started bool ctx context.Context cancel context.CancelFunc ServiceSpecs *sync.Map - entriesChan chan har.Entry + entriesChan chan EntryWithSource } diff --git a/agent/pkg/oas/specgen.go b/agent/pkg/oas/specgen.go index 72bd58994..feacc4f71 100644 --- a/agent/pkg/oas/specgen.go +++ b/agent/pkg/oas/specgen.go @@ -3,6 +3,11 @@ package oas import ( "encoding/json" "errors" + "fmt" + "github.com/chanced/openapi" + "github.com/google/uuid" + "github.com/nav-inc/datetime" + "github.com/up9inc/mizu/shared/logger" "mime" "net/url" "strconv" @@ -11,11 +16,13 @@ import ( "github.com/up9inc/mizu/agent/pkg/har" - "github.com/chanced/openapi" - "github.com/google/uuid" - "github.com/up9inc/mizu/shared/logger" + "time" ) +const LastSeenTS = "x-last-seen-ts" +const CountersTotal = "x-counters-total" +const CountersPerSource = "x-counters-per-source" + type reqResp struct { // hello, generics in Go Req *har.Request Resp *har.Response @@ -45,17 +52,23 @@ func NewGen(server string) *SpecGen { func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) { g.oas = oas + g.tree = new(Node) for pathStr, pathObj := range oas.Paths.Items { pathSplit := strings.Split(string(pathStr), "/") g.tree.getOrSet(pathSplit, pathObj) + + // clean "last entry timestamp" markers from the past + for _, pathAndOp := range g.tree.listOps() { + delete(pathAndOp.op.Extensions, LastSeenTS) + } } } -func (g *SpecGen) feedEntry(entry har.Entry) (string, error) { +func (g *SpecGen) feedEntry(entryWithSource EntryWithSource) (string, error) { g.lock.Lock() defer g.lock.Unlock() - opId, err := g.handlePathObj(&entry) + opId, err := g.handlePathObj(&entryWithSource) if err != nil { return "", err } @@ -70,10 +83,23 @@ func (g *SpecGen) GetSpec() (*openapi.OpenAPI, error) { g.tree.compact() - for _, pathop := range g.tree.listOps() { - if pathop.op.Summary == "" { - pathop.op.Summary = pathop.path + counters := CounterMaps{counterTotal: Counter{}, counterMapTotal: CounterMap{}} + + for _, pathAndOp := range g.tree.listOps() { + opObj := pathAndOp.op + if opObj.Summary == "" { + opObj.Summary = pathAndOp.path } + + err := counters.processOp(opObj) + if err != nil { + return nil, err + } + } + + err := counters.processOas(g.oas) + if err != nil { + return nil, err } // put paths back from tree into OAS @@ -81,6 +107,8 @@ func (g *SpecGen) GetSpec() (*openapi.OpenAPI, error) { suggestTags(g.oas) + g.oas.Info.Description = setCounterMsgIfOk(g.oas.Info.Description, &counters.counterTotal) + // to make a deep copy, no better idea than marshal+unmarshal specText, err := json.MarshalIndent(g.oas, "", "\t") if err != nil { @@ -169,7 +197,8 @@ func getPathsKeys(mymap map[openapi.PathValue]*openapi.PathObj) []string { return keys } -func (g *SpecGen) handlePathObj(entry *har.Entry) (string, error) { +func (g *SpecGen) handlePathObj(entryWithSource *EntryWithSource) (string, error) { + entry := entryWithSource.Entry urlParsed, err := url.Parse(entry.Request.URL) if err != nil { return "", err @@ -213,7 +242,7 @@ func (g *SpecGen) handlePathObj(entry *har.Entry) (string, error) { split = strings.Split(urlParsed.Path, "/") } node := g.tree.getOrSet(split, new(openapi.PathObj)) - opObj, err := handleOpObj(entry, node.pathObj) + opObj, err := handleOpObj(entryWithSource, node.pathObj) if opObj != nil { return opObj.OperationID, err @@ -222,7 +251,8 @@ func (g *SpecGen) handlePathObj(entry *har.Entry) (string, error) { return "", err } -func handleOpObj(entry *har.Entry, pathObj *openapi.PathObj) (*openapi.Operation, error) { +func handleOpObj(entryWithSource *EntryWithSource, pathObj *openapi.PathObj) (*openapi.Operation, error) { + entry := entryWithSource.Entry isSuccess := 100 <= entry.Response.Status && entry.Response.Status < 400 opObj, wasMissing, err := getOpObj(pathObj, entry.Request.Method, isSuccess) if err != nil { @@ -244,9 +274,86 @@ func handleOpObj(entry *har.Entry, pathObj *openapi.PathObj) (*openapi.Operation return nil, err } + err = handleCounters(opObj, isSuccess, entryWithSource) + if err != nil { + return nil, err + } + return opObj, nil } +func handleCounters(opObj *openapi.Operation, success bool, entryWithSource *EntryWithSource) error { + // TODO: if performance around DecodeExtension+SetExtension is bad, store counters as separate maps + counter := Counter{} + counterMap := CounterMap{} + prevTs := 0.0 + if opObj.Extensions == nil { + opObj.Extensions = openapi.Extensions{} + } else { + if _, ok := opObj.Extensions.Extension(CountersTotal); ok { + err := opObj.Extensions.DecodeExtension(CountersTotal, &counter) + if err != nil { + return err + } + } + + if _, ok := opObj.Extensions.Extension(CountersPerSource); ok { + err := opObj.Extensions.DecodeExtension(CountersPerSource, &counterMap) + if err != nil { + return err + } + } + + if _, ok := opObj.Extensions.Extension(LastSeenTS); ok { + err := opObj.Extensions.DecodeExtension(LastSeenTS, &prevTs) + if err != nil { + return err + } + } + } + + var counterPerSource *Counter + if existing, ok := counterMap[entryWithSource.Source]; ok { + counterPerSource = existing + } else { + counterPerSource = new(Counter) + counterMap[entryWithSource.Source] = counterPerSource + } + + started, err := datetime.Parse(entryWithSource.Entry.StartedDateTime, time.UTC) + if err != nil { + return err + } + + ts := float64(started.UnixNano()) / float64(time.Millisecond) / 1000 + rt := float64(entryWithSource.Entry.Time) / 1000 + + dur := 0.0 + if prevTs != 0 { + dur = ts - prevTs + } + + counter.addEntry(ts, rt, success, dur) + counterPerSource.addEntry(ts, rt, success, dur) + + err = opObj.Extensions.SetExtension(LastSeenTS, ts) + if err != nil { + return err + } + + err = opObj.Extensions.SetExtension(CountersTotal, counter) + if err != nil { + return err + } + + err = opObj.Extensions.SetExtension(CountersPerSource, counterMap) + if err != nil { + return err + } + + return nil +} + func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) error { // TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj @@ -500,3 +607,56 @@ func getOpObj(pathObj *openapi.PathObj, method string, createIfNone bool) (*open return *op, isMissing, nil } + +type CounterMaps struct { + counterTotal Counter + counterMapTotal CounterMap +} + +func (m *CounterMaps) processOp(opObj *openapi.Operation) error { + if _, ok := opObj.Extensions.Extension(CountersTotal); ok { + counter := new(Counter) + err := opObj.Extensions.DecodeExtension(CountersTotal, counter) + if err != nil { + return err + } + m.counterTotal.addOther(counter) + + opObj.Description = setCounterMsgIfOk(opObj.Description, counter) + } + + if _, ok := opObj.Extensions.Extension(CountersPerSource); ok { + counterMap := new(CounterMap) + err := opObj.Extensions.DecodeExtension(CountersPerSource, counterMap) + if err != nil { + return err + } + m.counterMapTotal.addOther(counterMap) + } + return nil +} + +func (m *CounterMaps) processOas(oas *openapi.OpenAPI) error { + if oas.Extensions == nil { + oas.Extensions = openapi.Extensions{} + } + + err := oas.Extensions.SetExtension(CountersTotal, m.counterTotal) + if err != nil { + return err + } + + err = oas.Extensions.SetExtension(CountersPerSource, m.counterMapTotal) + if err != nil { + return nil + } + return nil +} + +func setCounterMsgIfOk(oldStr string, cnt *Counter) string { + tpl := "Mizu observed %d entries (%d failed), at %.3f hits/s, average response time is %.3f seconds" + if oldStr == "" || (strings.HasPrefix(oldStr, "Mizu ") && strings.HasSuffix(oldStr, " seconds")) { + return fmt.Sprintf(tpl, cnt.Entries, cnt.Failures, cnt.SumDuration/float64(cnt.Entries), cnt.SumRT/float64(cnt.Entries)) + } + return oldStr +} diff --git a/agent/pkg/oas/test_artifacts/example.ldjson b/agent/pkg/oas/test_artifacts/example.ldjson index 629658a1d..27ef9d7f6 100644 --- a/agent/pkg/oas/test_artifacts/example.ldjson +++ b/agent/pkg/oas/test_artifacts/example.ldjson @@ -1,4 +1,4 @@ -{"messageType": "http", "_source": null, "firstMessageTime": 1627298057.784151, "lastMessageTime": 1627298065.729303, "messageCount": 12} +{"messageType": "http", "_source": "some-source", ",firstMessageTime": 1627298057.784151, "lastMessageTime": 1627298065.729303, "messageCount": 12} {"_id": "", "startedDateTime": "2021-07-26T11:14:17.78415179Z", "time": 13, "request": {"method": "GET", "url": "http://catalogue/catalogue/size?tags=", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "x-some", "value": "demo val"},{"name": "Host", "value": "catalogue"}, {"name": "Connection", "value": "close"}], "queryString": [{"name": "tags", "value": ""}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "22"}], "content": {"size": 22, "mimeType": "application/json; charset=utf-8", "text": "eyJlcnIiOm51bGwsInNpemUiOjl9", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 22}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 13}} {"_id": "", "startedDateTime": "2021-07-26T11:14:17.784918698Z", "time": 19, "request": {"method": "GET", "url": "http://catalogue/catalogue?page=1&size=6&tags=", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [{"name": "page", "value": "1"}, {"name": "size", "value": "6"}, {"name": "tags", "value": ""}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "1927"}], "content": {"size": 1927, "mimeType": "application/json; charset=utf-8", "text": "W3siaWQiOiIwM2ZlZjZhYy0xODk2LTRjZTgtYmQ2OS1iNzk4Zjg1YzZlMGIiLCJuYW1lIjoiSG9seSIsImRlc2NyaXB0aW9uIjoiU29ja3MgZml0IGZvciBhIE1lc3NpYWguIFlvdSB0b28gY2FuIGV4cGVyaWVuY2Ugd2Fsa2luZyBpbiB3YXRlciB3aXRoIHRoZXNlIHNwZWNpYWwgZWRpdGlvbiBiZWF1dGllcy4gRWFjaCBob2xlIGlzIGxvdmluZ2x5IHByb2dnbGVkIHRvIGxlYXZlIHNtb290aCBlZGdlcy4gVGhlIG9ubHkgc29jayBhcHByb3ZlZCBieSBhIGhpZ2hlciBwb3dlci4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9ob2x5XzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL2hvbHlfMi5qcGVnIl0sInByaWNlIjo5OS45OSwiY291bnQiOjEsInRhZyI6WyJhY3Rpb24iLCJtYWdpYyJdfSx7ImlkIjoiMzM5NWE0M2UtMmQ4OC00MGRlLWI5NWYtZTAwZTE1MDIwODViIiwibmFtZSI6IkNvbG91cmZ1bCIsImRlc2NyaXB0aW9uIjoicHJvaWRlbnQgb2NjYWVjYXQgaXJ1cmUgZXQgZXhjZXB0ZXVyIGxhYm9yZSBtaW5pbSBuaXNpIGFtZXQgaXJ1cmUiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJwcmljZSI6MTgsImNvdW50Ijo0MzgsInRhZyI6WyJicm93biIsImJsdWUiXX0seyJpZCI6IjUxMGEwZDdlLThlODMtNDE5My1iNDgzLWUyN2UwOWRkYzM0ZCIsIm5hbWUiOiJTdXBlclNwb3J0IFhMIiwiZGVzY3JpcHRpb24iOiJSZWFkeSBmb3IgYWN0aW9uLiBFbmdpbmVlcnM6IGJlIHJlYWR5IHRvIHNtYXNoIHRoYXQgbmV4dCBidWchIEJlIHJlYWR5LCB3aXRoIHRoZXNlIHN1cGVyLWFjdGlvbi1zcG9ydC1tYXN0ZXJwaWVjZXMuIFRoaXMgcGFydGljdWxhciBlbmdpbmVlciB3YXMgY2hhc2VkIGF3YXkgZnJvbSB0aGUgb2ZmaWNlIHdpdGggYSBzdGljay4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9wdW1hXzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL3B1bWFfMi5qcGVnIl0sInByaWNlIjoxNSwiY291bnQiOjgyMCwidGFnIjpbInNwb3J0IiwiZm9ybWFsIiwiYmxhY2siXX0seyJpZCI6IjgwOGEyZGUxLTFhYWEtNGMyNS1hOWI5LTY2MTJlOGYyOWEzOCIsIm5hbWUiOiJDcm9zc2VkIiwiZGVzY3JpcHRpb24iOiJBIG1hdHVyZSBzb2NrLCBjcm9zc2VkLCB3aXRoIGFuIGFpciBvZiBub25jaGFsYW5jZS4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18xLmpwZWciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18yLmpwZWciXSwicHJpY2UiOjE3LjMyLCJjb3VudCI6NzM4LCJ0YWciOlsiYmx1ZSIsImFjdGlvbiIsInJlZCIsImZvcm1hbCJdfSx7ImlkIjoiODE5ZTFmYmYtOGI3ZS00ZjZkLTgxMWYtNjkzNTM0OTE2YThiIiwibmFtZSI6IkZpZ3Vlcm9hIiwiZGVzY3JpcHRpb24iOiJlbmltIG9mZmljaWEgYWxpcXVhIGV4Y2VwdGV1ciBlc3NlIGRlc2VydW50IHF1aXMgYWxpcXVpcCBub3N0cnVkIGFuaW0iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9XQVQuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvV0FUMi5qcGciXSwicHJpY2UiOjE0LCJjb3VudCI6ODA4LCJ0YWciOlsiZ3JlZW4iLCJmb3JtYWwiLCJibHVlIl19LHsiaWQiOiI4MzdhYjE0MS0zOTllLTRjMWYtOWFiYy1iYWNlNDAyOTZiYWMiLCJuYW1lIjoiQ2F0IHNvY2tzIiwiZGVzY3JpcHRpb24iOiJjb25zZXF1YXQgYW1ldCBjdXBpZGF0YXQgbWluaW0gbGFib3J1bSB0ZW1wb3IgZWxpdCBleCBjb25zZXF1YXQgaW4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jYXRzb2Nrcy5qcGciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jYXRzb2NrczIuanBnIl0sInByaWNlIjoxNSwiY291bnQiOjE3NSwidGFnIjpbImJyb3duIiwiZm9ybWFsIiwiZ3JlZW4iXX1dCg==", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 1927}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 19}} {"_id": "", "startedDateTime": "2021-07-26T11:14:17.78418182Z", "time": 7, "request": {"method": "GET", "url": "http://catalogue/tags", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "107"}], "content": {"size": 107, "mimeType": "application/json; charset=utf-8", "text": "eyJlcnIiOm51bGwsInRhZ3MiOlsiYnJvd24iLCJnZWVrIiwiZm9ybWFsIiwiYmx1ZSIsInNraW4iLCJyZWQiLCJhY3Rpb24iLCJzcG9ydCIsImJsYWNrIiwibWFnaWMiLCJncmVlbiJdfQ==", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 107}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 7}} diff --git a/agent/pkg/oas/test_artifacts/params.har b/agent/pkg/oas/test_artifacts/params.har index 60a557414..f387f3b80 100644 --- a/agent/pkg/oas/test_artifacts/params.har +++ b/agent/pkg/oas/test_artifacts/params.har @@ -121,6 +121,120 @@ "connect": 262, "ssl": -1 } + }, + { + "startedDateTime": "2019-09-06T06:16:20.047122+00:00", + "time": 630, + "request": { + "method": "GET", + "url": "https://httpbin.org/appears-once", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "headersSize": 1542, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 39, + "compression": -20, + "mimeType": "application/json", + "text": "null" + }, + "redirectURL": "", + "headersSize": 248, + "bodySize": 39 + }, + "cache": {}, + "timings": { + "send": 14, + "receive": 4, + "wait": 350, + "connect": 262, + "ssl": -1 + } + }, + { + "startedDateTime": "2019-09-06T06:16:20.747122+00:00", + "time": 630, + "request": { + "method": "GET", + "url": "https://httpbin.org/appears-twice", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "headersSize": 1542, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 39, + "compression": -20, + "mimeType": "application/json", + "text": "null" + }, + "redirectURL": "", + "headersSize": 248, + "bodySize": 39 + }, + "cache": {}, + "timings": { + "send": 14, + "receive": 4, + "wait": 350, + "connect": 262, + "ssl": -1 + } + }, + { + "startedDateTime": "2019-09-06T06:16:21.747122+00:00", + "time": 630, + "request": { + "method": "GET", + "url": "https://httpbin.org/appears-twice", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "headersSize": 1542, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 39, + "compression": -20, + "mimeType": "application/json", + "text": "null" + }, + "redirectURL": "", + "headersSize": 248, + "bodySize": 39 + }, + "cache": {}, + "timings": { + "send": 14, + "receive": 4, + "wait": 350, + "connect": 262, + "ssl": -1 + } } ] }