// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xfs_test

import (
	"reflect"
	"strings"
	"testing"

	"github.com/prometheus/procfs/xfs"
)

func TestParseStats(t *testing.T) {
	tests := []struct {
		name    string
		s       string
		fs      bool
		stats   *xfs.Stats
		invalid bool
	}{
		{
			name: "empty file OK",
		},
		{
			name:  "short or empty lines and unknown labels ignored",
			s:     "one\n\ntwo 1 2 3\n",
			stats: &xfs.Stats{},
		},
		{
			name:    "bad uint32",
			s:       "extent_alloc XXX",
			invalid: true,
		},
		{
			name:    "bad uint64",
			s:       "xpc XXX",
			invalid: true,
		},
		{
			name:    "extent_alloc bad",
			s:       "extent_alloc 1",
			invalid: true,
		},
		{
			name: "extent_alloc OK",
			s:    "extent_alloc 1 2 3 4",
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 1,
					BlocksAllocated:  2,
					ExtentsFreed:     3,
					BlocksFreed:      4,
				},
			},
		},
		{
			name:    "abt bad",
			s:       "abt 1",
			invalid: true,
		},
		{
			name: "abt OK",
			s:    "abt 1 2 3 4",
			stats: &xfs.Stats{
				AllocationBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "blk_map bad",
			s:       "blk_map 1",
			invalid: true,
		},
		{
			name: "blk_map OK",
			s:    "blk_map 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1,
					Writes:               2,
					Unmaps:               3,
					ExtentListInsertions: 4,
					ExtentListDeletions:  5,
					ExtentListLookups:    6,
					ExtentListCompares:   7,
				},
			},
		},
		{
			name:    "bmbt bad",
			s:       "bmbt 1",
			invalid: true,
		},
		{
			name: "bmbt OK",
			s:    "bmbt 1 2 3 4",
			stats: &xfs.Stats{
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "dir bad",
			s:       "dir 1",
			invalid: true,
		},
		{
			name: "dir OK",
			s:    "dir 1 2 3 4",
			stats: &xfs.Stats{
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  1,
					Creates:  2,
					Removes:  3,
					Getdents: 4,
				},
			},
		},
		{
			name:    "trans bad",
			s:       "trans 1",
			invalid: true,
		},
		{
			name: "trans OK",
			s:    "trans 1 2 3",
			stats: &xfs.Stats{
				Transaction: xfs.TransactionStats{
					Sync:  1,
					Async: 2,
					Empty: 3,
				},
			},
		},
		{
			name:    "ig bad",
			s:       "ig 1",
			invalid: true,
		},
		{
			name: "ig OK",
			s:    "ig 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        1,
					Found:           2,
					Recycle:         3,
					Missed:          4,
					Duplicate:       5,
					Reclaims:        6,
					AttributeChange: 7,
				},
			},
		},
		{
			name:    "log bad",
			s:       "log 1",
			invalid: true,
		},
		{
			name: "log OK",
			s:    "log 1 2 3 4 5",
			stats: &xfs.Stats{
				LogOperation: xfs.LogOperationStats{
					Writes:            1,
					Blocks:            2,
					NoInternalBuffers: 3,
					Force:             4,
					ForceSleep:        5,
				},
			},
		},
		{
			name:    "rw bad",
			s:       "rw 1",
			invalid: true,
		},
		{
			name: "rw OK",
			s:    "rw 1 2",
			stats: &xfs.Stats{
				ReadWrite: xfs.ReadWriteStats{
					Write: 1,
					Read:  2,
				},
			},
		},
		{
			name:    "attr bad",
			s:       "attr 1",
			invalid: true,
		},
		{
			name: "attr OK",
			s:    "attr 1 2 3 4",
			stats: &xfs.Stats{
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    1,
					Set:    2,
					Remove: 3,
					List:   4,
				},
			},
		},
		{
			name:    "icluster bad",
			s:       "icluster 1",
			invalid: true,
		},
		{
			name: "icluster OK",
			s:    "icluster 1 2 3",
			stats: &xfs.Stats{
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     1,
					Flush:      2,
					FlushInode: 3,
				},
			},
		},
		{
			name:    "vnodes bad",
			s:       "vnodes 1",
			invalid: true,
		},
		{
			name: "vnodes (missing free) OK",
			s:    "vnodes 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
				},
			},
		},
		{
			name: "vnodes (with free) OK",
			s:    "vnodes 1 2 3 4 5 6 7 8",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
					Free:     8,
				},
			},
		},
		{
			name:    "buf bad",
			s:       "buf 1",
			invalid: true,
		},
		{
			name: "buf OK",
			s:    "buf 1 2 3 4 5 6 7 8 9",
			stats: &xfs.Stats{
				Buffer: xfs.BufferStats{
					Get:             1,
					Create:          2,
					GetLocked:       3,
					GetLockedWaited: 4,
					BusyLocked:      5,
					MissLocked:      6,
					PageRetries:     7,
					PageFound:       8,
					GetRead:         9,
				},
			},
		},
		{
			name:    "xpc bad",
			s:       "xpc 1",
			invalid: true,
		},
		{
			name: "xpc OK",
			s:    "xpc 1 2 3",
			stats: &xfs.Stats{
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 1,
					WriteBytes: 2,
					ReadBytes:  3,
				},
			},
		},
		{
			name:    "xstrat bad",
			s:       "xstrat 1",
			invalid: true,
		},
		{
			name: "xstrat OK",
			s:    "xstrat 1 2",
			stats: &xfs.Stats{
				Xstrat: xfs.XstratStats{
					Quick: 1,
					Split: 2,
				},
			},
		},
		{
			name:    "push_ail bad",
			s:       "push_ail 1 2 3 4 5",
			invalid: true,
		},
		{
			name: "push_ail OK",
			s:    "push_ail 1 2 3 4 5 6 7 8 9 10",
			stats: &xfs.Stats{
				PushAil: xfs.PushAilStats{
					TryLogspace:   1,
					SleepLogspace: 2,
					Pushes:        3,
					Success:       4,
					PushBuf:       5,
					Pinned:        6,
					Locked:        7,
					Flushing:      8,
					Restarts:      9,
					Flush:         10,
				},
			},
		},
		{
			name:    "debug bad",
			s:       "debug 1 2",
			invalid: true,
		},
		{
			name: "debug  OK",
			s:    "debug 1",
			stats: &xfs.Stats{
				Debug: xfs.DebugStats{
					Enabled: 1,
				},
			},
		},
		{
			name:    "qm bad",
			s:       "qm 1 2 3 4 5 6 7",
			invalid: true,
		},
		{
			name:    "qm bad",
			s:       "qm 1 2 3 4 5 6 7 8 9 10",
			invalid: true,
		},
		{
			name: "qm OK",
			s:    "qm 1 2 3 4 5 6 7 8",
			stats: &xfs.Stats{
				QuotaManager: xfs.QuotaManagerStats{
					Reclaims:      1,
					ReclaimMisses: 2,
					DquoteDups:    3,
					CacheMisses:   4,
					CacheHits:     5,
					Wants:         6,
					ShakeReclaims: 7,
					InactReclaims: 8,
				},
			},
		},
		{
			name: "qm OK",
			s:    "qm 1 2 3 4 5 6 7 8 9",
			stats: &xfs.Stats{
				QuotaManager: xfs.QuotaManagerStats{
					Reclaims:      1,
					ReclaimMisses: 2,
					DquoteDups:    3,
					CacheMisses:   4,
					CacheHits:     5,
					Wants:         6,
					ShakeReclaims: 7,
					InactReclaims: 8,
					Unused:        9,
				},
			},
		},
		{
			name:    "abtb2 bad",
			s:       "abtb2 1 2 3 4 5 6",
			invalid: true,
		},
		{
			name: "abtb2 OK",
			s:    "abtb2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
			stats: &xfs.Stats{
				BtreeAllocBlocks2: xfs.BtreeAllocBlocks2Stats{
					Lookup:    1,
					Compare:   2,
					Insrec:    3,
					Delrec:    4,
					NewRoot:   5,
					KillRoot:  6,
					Increment: 7,
					Decrement: 8,
					Lshift:    9,
					Rshift:    10,
					Split:     11,
					Join:      12,
					Alloc:     13,
					Free:      14,
					Moves:     15,
				},
			},
		},
		{
			name:    "abtc2 bad",
			s:       "abtc2 1 2 3 4 5 6",
			invalid: true,
		},
		{
			name: "abtc2 OK",
			s:    "abtc2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
			stats: &xfs.Stats{
				BtreeAllocContig2: xfs.BtreeAllocContig2Stats{
					Lookup:    1,
					Compare:   2,
					Insrec:    3,
					Delrec:    4,
					NewRoot:   5,
					KillRoot:  6,
					Increment: 7,
					Decrement: 8,
					Lshift:    9,
					Rshift:    10,
					Split:     11,
					Join:      12,
					Alloc:     13,
					Free:      14,
					Moves:     15,
				},
			},
		},
		{
			name:    "bmbt2 bad",
			s:       "bmbt2 1 2 3 4 5 6",
			invalid: true,
		},
		{
			name: "bmbt2 OK",
			s:    "bmbt2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
			stats: &xfs.Stats{
				BtreeBlockMap2: xfs.BtreeBlockMap2Stats{
					Lookup:    1,
					Compare:   2,
					Insrec:    3,
					Delrec:    4,
					NewRoot:   5,
					KillRoot:  6,
					Increment: 7,
					Decrement: 8,
					Lshift:    9,
					Rshift:    10,
					Split:     11,
					Join:      12,
					Alloc:     13,
					Free:      14,
					Moves:     15,
				},
			},
		},
		{
			name:    "ibt2 bad",
			s:       "ibt2 1 2 3 4 5 6",
			invalid: true,
		},
		{
			name: "ibt2 OK",
			s:    "ibt2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
			stats: &xfs.Stats{
				BtreeInode2: xfs.BtreeInode2Stats{
					Lookup:    1,
					Compare:   2,
					Insrec:    3,
					Delrec:    4,
					NewRoot:   5,
					KillRoot:  6,
					Increment: 7,
					Decrement: 8,
					Lshift:    9,
					Rshift:    10,
					Split:     11,
					Join:      12,
					Alloc:     13,
					Free:      14,
					Moves:     15,
				},
			},
		},
		{
			name: "fixtures OK",
			fs:   true,
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 92447,
					BlocksAllocated:  97589,
					ExtentsFreed:     92448,
					BlocksFreed:      93751,
				},
				AllocationBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1767055,
					Writes:               188820,
					Unmaps:               184891,
					ExtentListInsertions: 92447,
					ExtentListDeletions:  92448,
					ExtentListLookups:    2140766,
					ExtentListCompares:   0,
				},
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  185039,
					Creates:  92447,
					Removes:  92444,
					Getdents: 136422,
				},
				Transaction: xfs.TransactionStats{
					Sync:  706,
					Async: 944304,
					Empty: 0,
				},
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        185045,
					Found:           58807,
					Recycle:         0,
					Missed:          126238,
					Duplicate:       0,
					Reclaims:        33637,
					AttributeChange: 22,
				},
				LogOperation: xfs.LogOperationStats{
					Writes:            2883,
					Blocks:            113448,
					NoInternalBuffers: 9,
					Force:             17360,
					ForceSleep:        739,
				},
				ReadWrite: xfs.ReadWriteStats{
					Write: 107739,
					Read:  94045,
				},
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    4,
					Set:    0,
					Remove: 0,
					List:   0,
				},
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     8677,
					Flush:      7849,
					FlushInode: 135802,
				},
				Vnode: xfs.VnodeStats{
					Active:   92601,
					Allocate: 0,
					Get:      0,
					Hold:     0,
					Release:  92444,
					Reclaim:  92444,
					Remove:   92444,
					Free:     0,
				},
				Buffer: xfs.BufferStats{
					Get:             2666287,
					Create:          7122,
					GetLocked:       2659202,
					GetLockedWaited: 3599,
					BusyLocked:      2,
					MissLocked:      7085,
					PageRetries:     0,
					PageFound:       10297,
					GetRead:         7085,
				},
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 399724544,
					WriteBytes: 92823103,
					ReadBytes:  86219234,
				},
				PushAil: xfs.PushAilStats{
					TryLogspace:   945014,
					SleepLogspace: 0,
					Pushes:        134260,
					Success:       15483,
					PushBuf:       0,
					Pinned:        3940,
					Locked:        464,
					Flushing:      159985,
					Restarts:      0,
					Flush:         40,
				},
				Xstrat: xfs.XstratStats{
					Quick: 92447,
					Split: 0,
				},
				Debug: xfs.DebugStats{
					Enabled: 0,
				},
				QuotaManager: xfs.QuotaManagerStats{
					Reclaims:      0,
					ReclaimMisses: 0,
					DquoteDups:    0,
					CacheMisses:   0,
					CacheHits:     0,
					Wants:         0,
					ShakeReclaims: 0,
					InactReclaims: 0,
				},
				BtreeAllocBlocks2: xfs.BtreeAllocBlocks2Stats{
					Lookup:    184941,
					Compare:   1277345,
					Insrec:    13257,
					Delrec:    13278,
					NewRoot:   0,
					KillRoot:  0,
					Increment: 0,
					Decrement: 0,
					Lshift:    0,
					Rshift:    0,
					Split:     0,
					Join:      0,
					Alloc:     0,
					Free:      0,
					Moves:     2746147,
				},

				BtreeAllocContig2: xfs.BtreeAllocContig2Stats{
					Lookup:    345295,
					Compare:   2416764,
					Insrec:    172637,
					Delrec:    172658,
					NewRoot:   0,
					KillRoot:  0,
					Increment: 0,
					Decrement: 0,
					Lshift:    0,
					Rshift:    0,
					Split:     0,
					Join:      0,
					Alloc:     0,
					Free:      0,
					Moves:     21406023,
				},

				BtreeBlockMap2: xfs.BtreeBlockMap2Stats{
					Lookup:    0,
					Compare:   0,
					Insrec:    0,
					Delrec:    0,
					NewRoot:   0,
					KillRoot:  0,
					Increment: 0,
					Decrement: 0,
					Lshift:    0,
					Rshift:    0,
					Split:     0,
					Join:      0,
					Alloc:     0,
					Free:      0,
					Moves:     0,
				},

				BtreeInode2: xfs.BtreeInode2Stats{
					Lookup:    343004,
					Compare:   1358467,
					Insrec:    0,
					Delrec:    0,
					NewRoot:   0,
					KillRoot:  0,
					Increment: 0,
					Decrement: 0,
					Lshift:    0,
					Rshift:    0,
					Split:     0,
					Join:      0,
					Alloc:     0,
					Free:      0,
					Moves:     0,
				},
			},
		},
	}

	for _, tt := range tests {
		var (
			stats *xfs.Stats
			err   error
		)

		if tt.s != "" {
			stats, err = xfs.ParseStats(strings.NewReader(tt.s))
		}
		if tt.fs {
			xfs, err := xfs.NewFS("testdata/fixtures/proc", "testdata/fixtures/sys")
			if err != nil {
				t.Fatalf("failed to access xfs fs: %v", err)
			}
			stats, err = xfs.ProcStat()
			if err != nil {
				t.Fatalf("failed to gather xfs stats: %v", err)
			}
		}

		if tt.invalid && err == nil {
			t.Error("expected an error, but none occurred")
		}
		if !tt.invalid && err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) {
			t.Errorf("unexpected XFS stats:\nwant:\n%v\nhave:\n%v", want, have)
		}
	}
}
